[TUT] Modularizing C Code: Managing large projects

Go To Last Post
145 posts / 0 new

Pages

Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 2

For an updated version of this tutorial in PDF format, please see this page of my website.

Modularizing C Code: Managing large projects

Hi Freaks! Time for another half-baked tutorial! This one will focus on how to split up your C projects into manageable files (modules). It's by no means definitive, and serves as only a guide.

There comes a time when a project has outgrown the scope of a single file. Perhaps it's the point at with you need to add more RAM to your computer just to open your project, or when you realize you've just spent 45 minutes tracking down the timeout function you swore you wrote two weeks ago. Whatever the reason, it's time to split your project up into several manageable files, which you can then keep better track of. This tutorial will give several pointers about the intricacies of how to accomplish such a task.

Step one - identify common routines

First of all, it makes sense to group all the common routines into their own files - eg. one for USART management, one for your protocol layer handler, one for your main code. You should determine which functions can be moved into a separate files while still retaining some connection with one another. Let's take the following example of routines:

GetUSARTByte()
TurnOnStatusLed()
SetupSpeaker()
TurnOffStatusLed()
SetupUSART()
main()
CheckTempSensorVal()
ProcessByte()
Beep()
EnableUSART()
SleepMode()
CheckADCChanel()

We can split these routines up into several small files - one for the USART, one for the main (and misc) routines, one for the speaker and one for the ADC (and related processing).

Let's take a look at our proposed new structure:

USART.c

SetupUSART()
EnableUSART()
GetUSARTByte()
ProcessByte()

Speaker.c

SetupSpeaker()
Beep()

ADC.c

CheckADCChanel()
CheckTempSensorVal()

Main.c

main()
SleepMode()
TurnOnStatusLed()
TurnOffStatusLed()

Ok, looks pretty good! We'll cut and paste those routines into the appropriate files in the same project directory, so now we have a bunch of C files containing related functions.

Adding the new files to your makefile

Even though you've made no functional changes to your code outside moving certain routines to a different file, you still need to tell the compiler, linker and associated GCC tools where everything is now located. Open up your makefile, and you should find some lines similar to the following (if your makefile is based off the WinAVR template):

# List C source files here. (C dependencies are automatically generated.)

SRC = $(TARGET).c

What we now need to do is add the file names of the newly created files. We'll take our above example here again for consistency. Our new extra files are called "ADC.c", "Speaker.c" and "USART.c", so we need to add those to the SRC line of the makefile.

# List C source files here. (C dependencies are automatically generated.)

SRC = $(TARGET).c ADC.c USART.c Speaker.c

Now, that'll work, but it's a real pain for future expansion. To make our life easier, we'll place the filenames on their own line in alphabetical order. To indicate a line continuation in a makefile, we need to place a "\" at the end of the continued lines:

# List C source files here. (C dependencies are automatically generated.)

SRC = $(TARGET).c \
      ADC.c       \
      Speaker.c   \
      USART.c

NB: Watch the file case! To GCC and its related utilities, "MAIN.C" is not the same as "Main.c". Make sure you put down the file names in the exact same case as they appear inside explorer, or you'll get build errors!

Naming the routines

One problem with our multi-file setup remains; routines are still hard to find across the files. One might easily identify the file location of our mythical "CheckADCChanel()" routine, but what about "SleepMode()"? Obviously a naming convention becomes important.

Now, this is where it gets tricky. There's really no set standard for function names, and every one prefers something different. It is important to choose which one you prefer, but it is ten times more important to remain consistent in your scheme across your entire project.

The first word, symbol or acronym in your function name should indicate the file it is located in. For example, all the ADC functions will have "ADC" at their start, and all the speaker-related functions in Speaker.c would have "Speaker" at their start. You can eliminate repetition in a name as it becomes self-explanatory what the function is referring to due to the prefix - thus "CheckADCChanel()" would become "ADCCheckChannel()" (or similar), rather than the superfluous "ADCCheckADCChanel()".

I'll use our example again and put here a possible function naming scheme. Note that the "main()" function's name remains unchanged as it is mandated by the C standard:

USART.c

USARTSetup()
USARTEnable()
USARTGetByte()
USARTProcessByte()

Speaker.c

SpeakerSetup()
SpeakerBeep()

ADC.c

ADCCheckChanel()
ADCCheckTempSensorVal()

Main.c

main()
MainSleepMode()
MainTurnOnStatusLed()
MainTurnOffStatusLed()

This new scheme makes finding the location of a routine quick and easy, without the need for a multi-file search utility. Don't forget to change the function prototypes in your header file to the match your new function names!

Making functions static

Static functions is possibly something you've never heard of up to now. The process of declaring a function to be static indicates to the compiler that its scope is limited to the source file in which it is contained - in essence it makes the function private, only accessible by other function in the same C file. This is good practice from two standpoints; one, it prevents outside code from calling module-internal functions (and reduces the number of functions exposed by the file) and two, it gives the compiler a better chance to optimize the function in a better way that if it was assumed to be able to be called by any other source file.

Identify which of your functions are module-internal, and add in the static keyword. In our example, let's say that the "USARTEnable()" function was only called by "USARTSetup()", and never by any other source file. Assume that the USARTEnable function is declared as the following:

void USARTSetup(void)

We'll make the function static to reduce its scope to inside USART.c:

static void USARTEnable(void)

Note that the static keyword should be added to both the prototype in the header file, as well as to the function in the C source file.

Global variables

Your project probably has quite a few global variables declared at the top of your main source file. You need to cut-and-paste them each into the most appropriate source file. Don't worry if it's referenced by more than one file; dealing with that case will be handled later in the tutorial. For now, just put it in the C file you deem to be the best for it.

Just in case you've done this, remember the following golden rule: header files should declare, not define. A header file should not itself result directly in generated code - it is there to help the compiler and yourself link together your C code, located in C files. If you've put any globals inside your header file, move them out now.

Splitting the header file

This is where the hard part begins. So far we've successfully split up the contents of our project into several C files, but we're still stuck with one large header file containing all our definitions. We need to break our header file up into separate files for each of our C modules.

First, we'll create a bunch of .h header files of the same names as our .c files. Next, we'll move the obvious things from the master header file, the function prototypes. Copy each of the prototypes into the respective header files.

Next, the macros. Again, move the macros into the header file where they are used the most. If you have any generic macros that are used in the majority of your source files, create a new "GlobalDefinitions.h" header file and place them there. Do the same for any typedefs you may have in your header file.

Let's assume our above example has the following original header file:

#define StatusLedRed     (1 << 3)
#define StatusLedGreen     (1 << 2)
#define ADCTempSensChannel 5
#define BitMask(x) (1 << x)

typedef unsigned char USARTByte;

USARTByte GetUSARTByte(void);
void      TurnOnStatusLed(void);
void      SetupSpeaker(void);
void      TurnOffStatusLed(void);
void      SetupUSART(void);
int       main(void);
int       CheckTempSensorVal(void);
void      ProcessByte(USARTByte);
void      Beep(void);
void      EnableUSART(void);
void      SleepMode(void);
int       CheckADCChanel(char);

We can split this into our new header files like thus:

USART.h

typedef unsigned char USARTByte;

void        USARTSetup(void)
static void USARTEnable(void)
USARTByte   USARTGetByte(void)
void        USARTProcessByte(USARTByte)

Speaker.h

void SpeakerSetup(void)
void SpeakerBeep(void)

ADC.h

#define ADCTempSensChannel 5

int ADCCheckChanel(char)
int ADCCheckTempSensorVal(void)

Main.h

#define StatusLedRed     (1 << 3)
#define StatusLedGreen     (1 << 2)

int main(void)
void MainSleepMode(void)
void MainTurnOnStatusLed(void)
void MainTurnOffStatusLed(void)

Pretty straightforward - using our renamed functions, the job becomes easy. The only issue is the orphaned macro "BitMask", which we'll assume is used by all the files. As discussed, we'll place that into a separate header file called "GlobalDefinitions.h".

Renaming the macros

As with the function names, the macros should also be renamed to indicate where they are located. Again, use whatever convention you prefer.

Global variables revisited

Now comes the time to fix up those global variable references from before. You're faced with a problem; your global variable is declared in one file, but it's used in two or more. How do you tell the other files to use the declaration you already made?

The answer is the use of the "extern" keyword. This keyword indicates to the compiler that a global of the specified name and type has been declared elsewhere, and so the references are fixed up by the linker when the project is built. Inside the header file of the C module containing the global, add the same line of code (sans any initialization value) but add the extern keyword. Say we have the following:

USART.c

unsigned char LastCommand = 0x00;

We can add a reference to the global in USART.h to make it visible in our other C modules:

USART.h

extern unsigned char LastCommand;

Do this for each of your global variables that are accessed in C files other than the one in which it is contained.

Including the header files

Our final task is to link our header files to the relevant C files. Firstly, each .c file should include its own header file. In the C language, for header files located in the project's current directory, we use quotes around the file names:

USART.c

#include "USART.h"

Speaker.c

#include "Speaker.h"

ADC.c

#include "ADC.h"

Main.c

#include "Main.h"

Next, we need to include the library header files relevant to each C module. For example, our "Main.c" and "Speaker.c" files both make use of the "avr/io.h" header files. System headers should have angled brackets instead of quotes:

Speaker.h

#include 

Main.h

#include 

Rinse and repeat for each of the required system header files inside the header of each C module.

The last task of this step is to include the header files used by each module. For example, if a function in Main.c calls a function in ADC.c, we need our Main.h header file to include the ADC.h header file.

Final step - header file protection

The last thing to do before our conversion is complete, is to protect our header files from multiple inclusion. Take the following example. Say that Main and ADC both refer to each other:

Main.h

#include "ADC.h"

ADC.h

#include "Main.h"

What happens when this is compiled? The preprocessor will look at Main.h, then include ADC.h. However, ADC.h includes Main.h, which again includes ADC.h, etc...

To guard against this problem, we can use preprocessor defines. The following code snippet is the basic protection setup:

#ifndef MAIN_H
#define MAIN_H

// Header file contents

#endif

This construct, when applied to each of your header files, will protect against multiple inclusions. As each C file is compiled, the associated header file is included, as well as any other referenced header files (via includes in the C file's header file). As each header is included, a check is performed to see if the header's unique token is already defined, and if so the inclusion halts to prevent recursion. If the token is not already defined, the preprocessor defines it and looks at the remainder of the header file's contents. By giving each header file a different token (typically the header's filename in ALL CAPS, and the period replaced by an underscore), this system will prevent any preprocessor troubles.

Let's take a final look at how our mythical C file's headers might look like:

USART.h

#ifndef USART_H
#define USART_H

typedef unsigned char USARTByte;

void        USARTSetup(void)
static void USARTEnable(void)
USARTByte   USARTGetByte(void)
void        USARTProcessByte(USARTByte)

extern unsigned char LastCommand;

#endif

Speaker.h

#ifndef SPEAKER_H
#define SPEAKER_H

void SpeakerSetup(void)
void SpeakerBeep(void)

#endif

ADC.h

#ifndef ADC_H
#define ADC_H

#define ADC_TEMPSENSORCHANNEL 5

int ADCCheckChanel(char)
int ADCCheckTempSensorVal(void)

#endif

Main.h

#ifndef MAIN_H
#define MAIN_H

#define MAIN_STATLED_RED     (1 << 3)
#define MAIN_STATLED_GREEN   (1 << 2)

int  main(void)
void MainSleepMode(void)
void MainTurnOnStatusLed(void)
void MainTurnOffStatusLed(void)

#endif

Voila, we now have a separated, maintainable project!

I hope this tutorial is of some use. Feel free to post your corrections, questions and comments as always. Cheers all!

For an updated version of this tutorial in PDF format, please see this page of my website.

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

Last Edited: Sat. Feb 4, 2012 - 02:19 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Great tutorial. Just have to nitpick a little

abcminiuser wrote:
Just in case you've done this, remember the following golden rule: header files should define, not declare.

This should be the other way round. Hence
//Function Definition

return-type function-name(argument definitions)
{
    local variable definitions
    statements
}
//Goes in source file


//Function Declaration

return-type function-name(argument declarations); 

//Goes in header file 

Also

abcminiuser wrote:
Identify which of your functions are module-internal, and add in the static keyword. In our example, let's say that the "USARTEnable()" function was only called by "USARTSetup()", an never by any other source file. Assume that the USARTEnable function is declared as the following:
and then in the codesegments you declare USARTSetup to be static.

Again great tutorial.

Regards
-Andri Mar

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks Andri, fixed both of those issues. Glad to hear you liked it!

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Anyone who doesn’t know C will greatly benefit form your tutorial and the rest of us wish we had a tut like this when we first started learning C.
Very well written and concise, great job!
Keep up the good work.
Cheers
John

Resistance is futile…… You will be compiled!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have to second John's statement. I'm going back through my code with tutorial in-hand to see if there are areas where I can clean things up and make it more readable.

I like the idea of using the .c filename as the prefix for each of the routines inside that file. I know I don't do that 100% of the time, but it's a nice convention and I plan on using it from now on.

Thanks for the hard work, Dean! It's appreciated.

Tom

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the comments John and Tom. This is why I write these tutorials!

Tom: The function naming convention is up to you, but I DO strongly suggest you follow one. Use whatever you think is best, but be consistent and do make some reference to where the function is stored.

In my project, I use {ABBR}_{FNAME}, where "{ABBR}" is an abbreviation - actually acronym - of the file name. This means that a function "DoStuff" inside "SomeAmazingCFunctions.c" would be named "SACF_DoStuff". Use whatever works for you.

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Dean,

Our coding standard contains an extension of your function naming suggestion that others may find useful. Here's an excerpt from our standard:

http://www.ourcottage.plus.com/cs.html

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

i place all my functions in my header files.only a main.c;then many header files which contain the real codes.i think this is much more convenient,cleaner and easier than your method.

Our Martians are beginning to learn AVR

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Header files should NEVER directly output code when included - it's a bad idea all round. You might end up with naming conflicts, duplicated functions, etc and it's just a bad practice all round, just like including C files. Follow my guide here and you'll have a manageable large project which follows basic standards about multiple files.

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

i can not see your meaning clearly.

Quote:

Header files should NEVER directly output code when included

my project is so well now,just fine,working so smooth,doing the thing what i want.

Our Martians are beginning to learn AVR

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What happens when you want to #include the same .h file into two different .c files?

Remember that, in general, each .c file in a project is passed to the compiler and compiled in isolation. So if file1.c includes shared.h and that shared.h actually generates any code or data then file1.o will include a copy. Now you compile file2.c which also includes shared.h and the output file2.o ALSO now has copies of the generated data and functions. Hopefully the linker will recognise things as being identical but the chaces are you are going to get "xxxx is mutiply defined" errors during the link.

Cliff

PS I was about to point you towards an excellent tutorial about code modularity in the Tutorial Forum that explains how to do this properly - THEN I realised I was actually typing on the end of it! (obviously more caffeine needed here)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

yyw794 - just because it works, doesn't mean it is good! Feel free to do it your way, but if anyone else has to work with your code, expect to hear some complaints.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

last night i made it the same way as you mentioned above,but now the big problem appears! avrstudio4.13 tell me that "gcc plug-in:Error:Object file not found on the expected location ..."i have repaired my gcc and avrstudio4,but there is no help.what the hell is happening???

Our Martians are beginning to learn AVR

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

i make it ! i make it!! all of that is the nasty global variables.i fixed it ,and then ok

Our Martians are beginning to learn AVR

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For offline reference I've made a PDF file of Dean's tutorial and wanted to share it, you can find it below.

Ingo

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the comments John and Tom. This is why I write these tutorials!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Great tutorial, although I have a few newb questions:

dean: I may be wrong, but in all of the .h codes, in definitions like

void        USARTSetup(void) 
static void USARTEnable(void) 
USARTByte   USARTGetByte(void) 
void        USARTProcessByte(USARTByte)

you haven't used any ; dots. Is it supposed to be like that?

Also, now I'm getting some error that there's a multiple definition of one variable, although there's no multiple definition of the variable.

adc.o: In function `__vector_21':
../adc.c:4: multiple definition of `adc_channel_being_read'
main.o:../main.c:4: first defined here
make: *** [Sbot.elf] Error 1

and there's no definition of it outside that .h file
[edit] Nothing. solved the problem. Thanks for a lovely tutorial

[edit] xingpingli: WTF?

There are pointy haired bald people.
Time flies when you have a bad prescaler selected.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

dean: I may be wrong, but in all of the .h codes, in definitions like
Code:
void USARTSetup(void)
static void USARTEnable(void)
USARTByte USARTGetByte(void)
void USARTProcessByte(USARTByte)

you haven't used any ; dots. Is it supposed to be like that?

Intentional. I wanted to improve clarity by eliminating everything but the basics. I'm hoping the end reader realizes that they should use real, legal C code in their projects.

Seconding the WTF on xingpingli. Did your reply get chopped off?

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

David,

Sorry to be a pedant but you mean you are DECLARing and not DEFINing adc_channel_being_read in the .h don't you?

IOW just ONE of your .c files should contain the definition:

type adc_channel_being_read;

while the shared .h file should have the declaration:

extern type adc_channel_being_read;

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

cliff:
It's OK now. I fortunately found it out. Never used this kind of program structuring before. I've also had a problem, that the ADC part will be trying to use some of the USART code. It gave me an unknown reference. Is including the "usart.h" everywhere where it is needed the right way, or should there be one file with all of the function prototypes?

There are pointy haired bald people.
Time flies when you have a bad prescaler selected.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, the "right" way is to include all the required header files. If you've structured your project as this tutorial teaches, including the header files should do nothing more than expose typedefs, enums, macros, prototypes and variable declarations - no code size penalty. If you *really* want, you can make public and private headers for each file, with only the items that need to be globally exposed in the public headers.

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

If you *really* want, you can make public and private headers for each file, with only the items that need to be globally exposed in the public headers.

I've sometimes wondered if I should be doing this. But how would one keep the private header private? Simply not mention it in documentation? Or add a big comment "Should only be included in foo.c"? Splitting things that are only needed in one file to another file 'feels' wrong even though it would follow the rule that declarations go to header files.

So I usually put declarations of static functions and constants only needed in one place to the top of the corresponding .c file. I would appreciate any advice or comments others may have on this.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In our large projects a module will generally have a three letter acronym (e.g. SPI) and in the \spi directory there will be various spi???.c files, an spi.h that is the "public" header file that may be #include'd by other modules or main code in the project then any .h stuff that other modules don't need to see will go into spii.h (the added i for "internal"). While the tla???.c files will probably include tla.h and tlai.h, outside of that directory the C files will only include tla.h (they may know that tlai.h exists but would never use it directly)

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi All,
Again a great tutorial here.
Where should I best store these new source and header files in Winavr ?
Where to configure the path to these files ?
Do I have to configere the path ones ?
Or do I have to add Header and Source files in the left
column named "AVR GCC" in AvrStudio for each new project that needs these files ?
Thanks,

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Go4It,

Thanks for the compliment.

Quote:
Where should I best store these new source and header files in Winavr ?
Where to configure the path to these files ?
Do I have to configere the path ones ?
Or do I have to add Header and Source files in the left
column named "AVR GCC" in AvrStudio for each new project that needs these files ?

I don't have much experience in using AVRStudio as a GCC frontend, so the following is only what I believe to be the case. I'm sure someone else will correct me if I get things wrong.

You should put your source files in the same directory as your main project source file, to keep things neat and ordered. You should then add them to the left AVRStudio source file pane, to tell AVRStudio to compile and link them in with your main source file.

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

abcminiuser wrote:

Quote:
Where should I best store these new source and header files in Winavr ?
Where to configure the path to these files ?
Do I have to configere the path ones ?
Or do I have to add Header and Source files in the left
column named "AVR GCC" in AvrStudio for each new project that needs these files ?

[...]You should put your source files in the same directory as your main project source file, to keep things neat and ordered. You should then add them to the left AVRStudio source file pane, to tell AVRStudio to compile and link them in with your main source file.


Right click on 'Source files' -> 'Add existing source file(s)' to do this and add all .c files.

Then add the #include statements for the corresponding .h files on the top of your main source file.

Ingo

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Dean,

Our coding standard contains an extension of your function naming suggestion that others may find useful. Here's an excerpt from our standard:

http://www.ourcottage.plus.com/cs.html

quote from your link:

Quote:

Note: C doesn’t support the concept of a module global, this naming convention allows the
distinction between a system global which would be used by other modules from a module
global which should not be accessed by other modules

Isnt it so that making a module level variable static means its global to module level and inaccessible from other modules?

For that matter, not declaring the variable in the modules header file will generate an 'undeclared ...' warning when it is used from outside the module. And we always want zero warnings right? :)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

abcminiuser wrote:
If you *really* want, you can make public and private headers for each file, with only the items that need to be globally exposed in the public headers.

- Dean :twisted:

Private declarations belong in the source file itself, not in a header. This is the only way of ensuring that no other module knows about the private declarations.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi!

Can I make one big Common.h for all .c files?
Common.h would contain:

-#include
-all function's prototype,
-extern.. global vars (these are defined in the beginning of main.c)

Common.h would be included in every *.c file in the project.
So there will be multiple prototypes, global var declarations.
But is this a problem?

Thx

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You can do whatever you like and I can't do anything to stop you ;).

However, I'd recommend against it. Generally, it's considered good practice to go with the normal one header per source file route. That way each source file only includes what is needed, preventing multiple definition problems and speeding up compilation.

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

mrx23dot wrote:

But is this a problem?

Dean wrote:

[...]go with the normal one header per source file route. That way each source file only includes what is needed, preventing multiple definition problems and speeding up compilation.

And apart fromthose two (valid) points, ponder this: What happends when you want to take one piece of the developed source code and use in another project. Example: Some code to handle a "ring buffer" "on top" of the UART, or maybe some code to handle a LCD display. If all your prototypes are in one big header file you now have to copy the parts you want and paste them into another header file for the new project. If you had a separate RingBuffer.h or LCD.h (to accompany your RingBuffer.c or LCD.c) you'd just make a copy of the files to the new project.

And after doing that for a while you get problems with those copies drifting apart leading to eg. bugs fixed in one project being un-fixed in another. You realise that you need those files stored in one master place. Every time you fix a bug or make an enhancement you'd update the master files, test them and then distribute them to all projects that are using them.

(And after doing that for a while you'd stop distributing the sources to the different projects and instead build librarys of precompiled code to distribute. Or you might set up your own version control system, eg. Subversion and store the files there...)

Anyhow, my point is - it's not only about compiler efficiency and avoiding build errors. Its just as much about making code reuse easy, efficient and less error-prone.

"Ziggy really sang, screwed up eyes and screwed down hairdo. Like some cat from Japan, he could lick 'em by smiling. He could leave 'em to hang. He came on so loaded man, well [..] and snow white tan. [Bowie]

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't have a .h file for every .c file but one .h file for every "module" that may actually contain a number of .c files. So I might have ADC support in a single adc.c with a corresponding adc.h but for something like a flash filing system there could be 10 or more .c files and a single ffs.h

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Awesome tutorial. Just what the doctor ordered.

Thanks Dean.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Guys,

I've been working on a project for uni and have been putting all my code into the one .c file and the one .h file.

However, it was all getting a bit messy, so I decided to try and break it all up into seperate .c and .h files for ease of documentation.

However, I'm now getting an issue when I try and compile.
I get messages such as

../ISR.c:13: In function '__vector_6':
../ISR.c:31: error: 'distance_string' undeclared (first use in this function)

'distance_string' is declared in another header file (distance_measure.h) with the 'extern' keyword, so I don't understand why AVR Studio isn't linking it correctly. Do I have to include distance_measure.h in isr.h? Through reading Dean's tutorial it makes it sound like I only do this if I am calling a function from another file.

Can anyone help me out with some ideas to try?
If I need to post the code I will, it's just that it's all a bit messy at the moment :wink:

Thanks!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You've got distance_measure.h included in ISR.c?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I would like to add a tip that will save you a lot of time in Programmers Notepad. There is an easy way to prepend extern to long lists of declarations:

1)Make a vertical list of the word "extern " (note the space character) on the left margin using copy and paste
2)Hold down the Alt key and click and drag a box around the list you just made. The list should be selected now.
3)Cut
4)Place the cursor at the beginning of the list of declarations
5)Paste

If you do this, PN should have prepended your declarations with "extern ". I hope this is helpful to someone.

edit: I just figured out that it works in AVR Studio also, only you have to continue holding Alt until after you cut. And also you have to issue the cut command with the mouse not the keyboard.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Do I have to include distance_measure.h in isr.h?

Include it in isr.c, as hinted at by davef.

We see questions similar to this on and off here at 'freaks, and what you really need to know to analyze the situation yourself (rather than us just giving a cook-book recipe for resolving it) is that all includes are handled by the preprocessor before the compiler proper starts looking at the source. Wherever an #include is done, in that place the whole header file is inserted into the source code (and nested includes work that way too). So when the compiler proper starts working there are no #includes left in the "compilation unit".

Now, in your ISR.c, on line 31, the declaration of distance_string must aready have been seen by the compiler.

If you are really curious there is a switch to the gcc compiler to produce a listing file which shows what the compiler proper sees after the preprocessor has "mangled" the file. I don't recall the syntax of the switch - go to the (avr-)gcc documentation if you really want to try it out.

"Ziggy really sang, screwed up eyes and screwed down hairdo. Like some cat from Japan, he could lick 'em by smiling. He could leave 'em to hang. He came on so loaded man, well [..] and snow white tan. [Bowie]

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks JohanEkdahl and davef for your replies.

However, now I have another problem. :(

I started getting alot more error messages saying things like:

ISR.c: (.text+0x1a): undefined reference to 'distance_string'

and for other global variables.

I went and removed the 'extern' keyword from all the global variables declared in the different header files just to see what happened, and the error messages changed to

 ... multiple definition of...

Now I don't know why this is occurring either, because the variables are only declared in ONE header file, and I have put

 #ifndef DISTANCE_MEASURE_H
#define DISTANCE_MEASURE_H
...
#endif

in each of the header files.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The "undefined reference to X" is probably because the source file that actually defines (implements) X is not included in the project. While compiling, the compiler only sees

extern type X

and goes "Oh, allright it is somewhere, but I don't know where. Another instance of me the compiler will see it while compiling some other source file, and we both will leave it to the linker to resolve this." As that other file never is compiled the linker will not see it and goes "Darn, here's a reference that I cannot resolve".

When you remove all those extern from the declarations they all turn into definitions (implementations) of X. Now every time the compiler sees one of those it will emit code for it into the object file. And when the linker gets hold a hold of thyose object files it has to emit an error becuse it sees the same variable implemented several times.

The solution to the first problem is not to delete all "extern" thingies, but to locate the source file that actually implements X and see to it that it gets compiled and linked in.

"Ziggy really sang, screwed up eyes and screwed down hairdo. Like some cat from Japan, he could lick 'em by smiling. He could leave 'em to hang. He came on so loaded man, well [..] and snow white tan. [Bowie]

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sorry when I say I've put

#ifndef DISTANCE_MEASURE_H
...

I mean I've named them differently depending on the name of the header file (eg. ISR_H for isr.h, etc etc etc)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks to your help JohanEkdahl, and a bit of fiddling... I managed to get it to all compile (and probably more important learned something along the way :) ).

Now to start playing with this doxygen to get some some nice documentation out of it...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Glad to be back here... :)

Im not sure if this may help but, one may also use function table in declaring functions, specially those that almost have the same characteristics (ie communication protocols that sends, receives data).

For example:

From Protocol.h
#define INIT   0
#define TXDATA 1
#define RXDATA 2


From Protocol.c
void (*Protocol[][Idx])() =
{
#if defined (UART)
    ProtocolInit[Idx] = UartInit();
    ProtocolTxData[Idx] = UartTxData();
    ProtocolRxData[Idx] = UartRxData();

#elif
    ProtocolInit[Idx] = SpiInit();
    ProtocolTxData[Idx] = SpiTxData();
    ProtocolRxData[Idx] = SpiRxData();
#endif
}

The Idx refers to the UART channel.

From the calling function, one may only need to call

Protocol[INIT][Idx]

if the UART or SPI needs to be initialized, depending on the channel (Idx). If you are using 2 UART, you set Idx to either 0 or 1, depending on which UART you want to initialize.

Protocol[TXDATA][Idx]

to transmit data,
and

Protocol[RXDATA][Idx]

to get the data.

I haven't compiled this code yet so there might be some missing or erroneous syntax. Please feel free to correct me.

Thanks. :)

Nice tutorial, dean.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Header files should NEVER directly output code when included

Quote:

What happends when you want to take one piece of the developed source code and use in another project.

Indeed. How does a statement like #include allow you to use functions like printf()?

Thanks,

-Stephen

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for this very good tutorial! :D

I'm just re-organising a large project, but I've got some problems with global vars.

this is my situation:
I've got several source files with their headers
main(.c .h) UART(.c.h) EEPROM(.c .h) ...
I've got a Constants.h with some defines that are used in the whole project and need to be included everywhere, as th GlobalHeader.h where i put some type defs and library includes.

1) those headers that go everywhere stay better in c files or in every header? (i.e. changes something if I put Constants.h in main.c or main.h etc...)

2) I've got a compilation error (undeclared variable) on global vars. I've got some global (whole project) vars which I declared at the beginning of the main.c as

uint8_t checkSend;

and in the main.h as

extern uint8_t checkSend;

now I got errors compiling UART.c whic uses those global vars. Do I need to put the extern uint8_t checkSend; in headers of every file wich uses this var?

Thanks,
-bo.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You should have a vars.h (or something) that documents the globally accessible variables in main.c and #include this wherever one or more of those variables need to be accessed.

It should only be necessary to have the extern declaration of any particular variable definition in a .c file made in one .h file - then #include that file where visibility is required.

If it were me I think I'd #include main.h, uart.h and eeprom.h (perhaps also etc) in globalheaders.h and then just #include that one file in each .c file.

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ok, thanks!
I'll include those vars as a sub section of the GlobalHeader.h, which goes everywhere...
bye,
bo.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A few other little hints along the same lines.

* Include as FEW header files in other files as far as is possible for several reasons:

(1) You can quickly tell what modules are **really** dependant on other modules.

(2) Compile time. OK, AVR compile time is relatively short, but wait until you do a complex C++ project using lots and lots of template functions. You'll definitely appreciate this rule then!

* Include the header files in order of custom.h through to the standard libraries. You'll have less dependency problems when porting the sources to something else.

When separating out functionality, I usually group related files in a directory. It then makes it very easy to branch this directory into another project and track/merge changes between the two of them. See comments on a source control system below :)

And, not directly related to C programming (so I expect to get flamed), but I strongly recommend the following:

(a) Learn C++, or a reasonably close-to-metal Object-oriented Language, even if it's for doing basic host-side work. My C code (and even assembly!) is cleaner, more portable and far more maintainable after learning an OO language and putting various practices in place.

(b) There is no excuse whatsoever (and I really, really mean that) for not using a version control system. Even if you're an only-programmer or hobbyist on a project. One day, you'll break something and wonder what you did to break it.

Subversion is free, open source and cross-platform, both command line and graphical for Windows and *nix.

(c) C99 includes nice features like inline, restrict and const that, when used correctly, will make code safer and faster in many scenarios.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Subversion is free, open source and cross-platform, both command line and graphical for Windows and *nix.

Agree and if using Windows then Tortoise is a nice interface. But if you are in Linux also consider Git which is the new pretender to the SVN crown (possibly because it was written by Linus Torvalds with the needs of version managing the Linux kernel in mind)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the great tutorial Dean!!

Very useful and clear!!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
But if you are in Linux also consider Git

AFAIK Git is available on Windows also.

Quote:

consider Git which is the new pretender to the SVN crown

I'd say that there is a troika of contenders: Bazaar, Git and Mercurial. There are Windows Explorer-integrated GUIs available for all three (TortoiseBzr, TortoiseGit and TortoiseHg).

"Ziggy really sang, screwed up eyes and screwed down hairdo. Like some cat from Japan, he could lick 'em by smiling. He could leave 'em to hang. He came on so loaded man, well [..] and snow white tan. [Bowie]

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JohanEkdahl wrote:

I'd say that there is a troika of contenders: Bazaar, Git and Mercurial. There are Windows Explorer-integrated GUIs available for all three (TortoiseBzr, TortoiseGit and TortoiseHg).

We'll leave the revision control wars for another thread :P

In the meantime, a good list can be found at http://en.wikipedia.org/wiki/Lis...

Pages