Forum Menu




 


Log in Problems?
New User? Sign Up!
AVR Freaks Forum Index

Post new topic   Reply to topic
View previous topic Printable version Log in to check your private messages View next topic
Author Message
abcminiuser
PostPosted: Apr 09, 2007 - 02:12 PM
Moderator


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway

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:

Code:
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
Code:
SetupUSART()
EnableUSART()
GetUSARTByte()
ProcessByte()


Speaker.c
Code:
SetupSpeaker()
Beep()


ADC.c
Code:
CheckADCChanel()
CheckTempSensorVal()


Main.c
Code:
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):

Code:
# 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.

Code:
# 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:

Code:
# 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
Code:
USARTSetup()
USARTEnable()
USARTGetByte()
USARTProcessByte()


Speaker.c
Code:
SpeakerSetup()
SpeakerBeep()


ADC.c
Code:
ADCCheckChanel()
ADCCheckTempSensorVal()


Main.c
Code:
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:

Code:
void USARTSetup(void)


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

Code:
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:

Code:
#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
Code:
typedef unsigned char USARTByte;

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


Speaker.h
Code:
void SpeakerSetup(void)
void SpeakerBeep(void)


ADC.h
Code:
#define ADCTempSensChannel 5

int ADCCheckChanel(char)
int ADCCheckTempSensorVal(void)


Main.h
Code:
#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
Code:
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
Code:
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
Code:
#include "USART.h"


Speaker.c
Code:
#include "Speaker.h"


ADC.c
Code:
#include "ADC.h"


Main.c
Code:
#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
Code:
#include <avr/io.h>


Main.h
Code:
#include <avr/io.h>


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
Code:
#include "ADC.h"


ADC.h
Code:
#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:

Code:
#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
Code:
#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
Code:
#ifndef SPEAKER_H
#define SPEAKER_H

void SpeakerSetup(void)
void SpeakerBeep(void)

#endif


ADC.h
Code:
#ifndef ADC_H
#define ADC_H

#define ADC_TEMPSENSORCHANNEL 5

int ADCCheckChanel(char)
int ADCCheckTempSensorVal(void)

#endif


Main.h
Code:
#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 Evil

_________________
Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.


Last edited by abcminiuser on Feb 04, 2012 - 02:19 PM; edited 3 times in total
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
andrimar
PostPosted: Apr 09, 2007 - 04:28 PM
Rookie


Joined: Feb 12, 2006
Posts: 45
Location: Reykjavík, Iceland

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
Code:
//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
 
 View user's profile Send private message  
Reply with quote Back to top
abcminiuser
PostPosted: Apr 10, 2007 - 12:52 AM
Moderator


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway

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

- Dean Twisted Evil

_________________
Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
AllN
PostPosted: Apr 10, 2007 - 08:20 AM
Posting Freak


Joined: Feb 14, 2007
Posts: 1858
Location: San Diego California

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!
 
 View user's profile Send private message  
Reply with quote Back to top
benedict
PostPosted: Apr 10, 2007 - 08:59 PM
Hangaround


Joined: Sep 11, 2006
Posts: 163
Location: Kamuela, HI

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
 
 View user's profile Send private message  
Reply with quote Back to top
abcminiuser
PostPosted: Apr 11, 2007 - 03:35 AM
Moderator


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway

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 Evil

_________________
Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
clawson
PostPosted: Apr 11, 2007 - 11:01 AM
10k+ Postman


Joined: Jul 18, 2005
Posts: 62354
Location: (using avr-gcc in) Finchingfield, Essex, England

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

_________________
 
 View user's profile Send private message  
Reply with quote Back to top
yyw794
PostPosted: Jul 02, 2007 - 08:00 AM
Wannabe


Joined: Jan 20, 2007
Posts: 69
Location: Mars

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
 
 View user's profile Send private message Visit poster's website 
Reply with quote Back to top
abcminiuser
PostPosted: Jul 02, 2007 - 08:07 AM
Moderator


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway

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 Evil

_________________
Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
yyw794
PostPosted: Jul 02, 2007 - 08:48 AM
Wannabe


Joined: Jan 20, 2007
Posts: 69
Location: Mars

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
 
 View user's profile Send private message Visit poster's website 
Reply with quote Back to top
clawson
PostPosted: Jul 02, 2007 - 11:03 AM
10k+ Postman


Joined: Jul 18, 2005
Posts: 62354
Location: (using avr-gcc in) Finchingfield, Essex, England

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)

_________________
 
 View user's profile Send private message  
Reply with quote Back to top
Kartman
PostPosted: Jul 04, 2007 - 05:33 AM
Raving lunatic


Joined: Dec 30, 2004
Posts: 8788
Location: Melbourne,Australia

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.
 
 View user's profile Send private message  
Reply with quote Back to top
yyw794
PostPosted: Jul 06, 2007 - 04:27 AM
Wannabe


Joined: Jan 20, 2007
Posts: 69
Location: Mars

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
 
 View user's profile Send private message Visit poster's website 
Reply with quote Back to top
yyw794
PostPosted: Jul 06, 2007 - 05:36 AM
Wannabe


Joined: Jan 20, 2007
Posts: 69
Location: Mars

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
 
 View user's profile Send private message Visit poster's website 
Reply with quote Back to top
ikletti
PostPosted: Jul 06, 2007 - 03:17 PM
Hangaround


Joined: Oct 17, 2003
Posts: 311
Location: Germany

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

Ingo
 
 View user's profile Send private message  
Reply with quote Back to top
xingpingli
PostPosted: Jul 15, 2007 - 06:39 AM
Newbie


Joined: Jul 15, 2007
Posts: 1


Thanks for the comments John and Tom. This is why I write these tutorials!
 
 View user's profile Send private message  
Reply with quote Back to top
daqq
PostPosted: Jul 15, 2007 - 08:37 AM
Raving lunatic


Joined: Dec 15, 2003
Posts: 4404
Location: Slovakia, Bratislava

Great tutorial, although I have a few newb questions:

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?

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.

Code:
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.
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
abcminiuser
PostPosted: Jul 15, 2007 - 10:02 AM
Moderator


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway

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 Evil

_________________
Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
clawson
PostPosted: Jul 15, 2007 - 02:49 PM
10k+ Postman


Joined: Jul 18, 2005
Posts: 62354
Location: (using avr-gcc in) Finchingfield, Essex, England

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:
Code:
type adc_channel_being_read;

while the shared .h file should have the declaration:
Code:
extern type adc_channel_being_read;

_________________
 
 View user's profile Send private message  
Reply with quote Back to top
daqq
PostPosted: Jul 15, 2007 - 05:32 PM
Raving lunatic


Joined: Dec 15, 2003
Posts: 4404
Location: Slovakia, Bratislava

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.
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
Display posts from previous:     
Jump to:  
All times are GMT + 1 Hour
Post new topic   Reply to topic
View previous topic Printable version Log in to check your private messages View next topic
Powered by PNphpBB2 © 2003-2006 The PNphpBB Group
Credits