Keep it simple in list of large no of files

Discussion in 'The Projects Forum' started by aamirali, Nov 29, 2012.

  1. aamirali

    Thread Starter Member

    Feb 2, 2012

    I am making a project on cortex-m3 mcu. It has hell lot of function- some of like 2 timers,stepper,printer,lcd,rtc,eeprom,sdhc,mcp3208,internal adc, keypad & some others.

    Now total no of files added upto 18. Now I am getting bit of lost. To make small changes I have look in many files. As chnage has to do with 2 or 3 modules.

    Like I have upgraded IC from 48 to 64 pin. Now pin setting has changed. I had to look in all the files about this.

    Any suggestions/idea/standard which should to be follow to avoid mess.
  2. Wendy


    Mar 24, 2008
    The Completed Projects Forum is for Completed Projects only. It is meant to allow members to show plans for projects they built so other members can duplicate them if desired. New threads are also automatically moderated per Moderator review for this reason. Your thread does not belong in this forum, and was moved here.
  3. MrChips


    Oct 2, 2009
    Which chip and compiler are you using?
  4. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    I don't use that series of chips, but in Microchip's libraries they use an include file called "hardware_profile.h" where the fine details of what pins are being used for what can be detailed all in one place.

    That is a very effective scheme I have used in many projects.
  5. aamirali

    Thread Starter Member

    Feb 2, 2012
    lpc1317 + keil
  6. JohnInTX


    Jun 26, 2012
    I hate to say it but it sounds like you don't have enough files, or at least they need to be better organized. My particular way is this:

    Organize each system function (not system resources like timers, ADCs etc.) in one file or set of like-named files. All routines for that function are in that file(s). Typical files would always include an init routine, and others that provide related functionality. For example, the LCD file would have initLCD() which when called, completely initializes the LCD from any condition. It would also include primitives such as write a character, configure the cursor, read the current location etc. whatever your application requires. The file should be named LCDxxx.c where xxx is descriptive text. If your LCD code gets too big, split it up into levels i.e. LCDdriver.c (low level hardware stuff only) LCDstrings.c (basic character I/O) LCDscreens.c (higher level, sets up standard user interface formats to write to) etc. with each level having more functionality and drawing on lower levels. High level routines that want to say something to the user like

    Code ( (Unknown Language)):
    1. alert("High Pressure");
    should not have to worry about what's doing the alerting, just what it does or how it does it, or what size the LCD is. In this case, the 'alert' function is abstract.. today it makes a screen (by sending commands to the display), highlights the top line (more commands and locates the cursor), then writes the text. Tomorrow, maybe it sends an email. Doesn't matter. If you do your code in levels and abstractions, these changes are easy. Beware of code that does these things in the system flow.. but that's another topic..

    Note also that ALL system resources the LCD uses are also contained in the LCD file. Got a timer IRQ service to flash something? That code (init, set the time interval etc.) belongs in LCD, not some timer file.. The goal is when you need to change the LCD, all LCD stuff is in one place, including non-LCD peripherals that the LCD uses for its function.

    Since all system functionality is now in individual files, you would call ONE init routine for each function i.e. initLCD, initCANBUS, initUART etc etc. And of course, all of that is contained in InitSYSTEM.c.. (for me anyway). What to know if you have forgotten to init something? That's where to look.

    My overriding goal is to make main() and its called files simply a framework that looks a lot like my block diagram describing the system and have it call all other support routines. Main should never have to worry about the details... just init, writeLCDstring, getUSERinput, processUSERinput.. etc. All else is deferred to lower levels in organized files.

    I also put all derived types (typedefs) in a single file projectTYPES.h so that I have a single place to look for them. Don't scatter them around in various header files. Same goes for global variables.

    I have a single IO definition file IODEFxxx.h. ALL system IO is defined here along with macros to help things along. I never use things like LATB,7=1 in the high level code. I define a macro that says RELAY_ON that the macro expands in the code from the IODEFxxx file. That way 1) the code is more descriptive and 2) I can substitute other IODEF.. files to handle different pinouts, number of pins, relay drivers etc. without getting into my control source. Having ALL IO defs in one place allows the whole file to be switched out for different chips, PCB revs etc. Which file is used is selected by.. CONFIG.

    I always have two CONFIG files. One is used in development, the other is used when its time to release the code. They resolve any conditionals in the code and include various versions of files depending on which configuration I am building. They contain the same stuff, only the development version is for monkeying around with during development for tests and the CONFIG_RELEASE version has all of the proper switches set for release. That way I don't have to remember to un-set test code etc. Just flip a switch, rebuild and ship it. Nice.

    My filenames are not accidental. I use uppercase for the basic function i.e. LCD, KBD, UART etc with lowercase to indicate any supplemental files. That way, the project file list will guide you to where the code for the FUNCTION is. Again, the LCD may use TMR0 and TMR2 but there are no files named TMR0 and TMR2. Those system resources are used and maintained in LCDxxx files.

    Looking at my last project, I have 62 .C files, and 70 .h files (62 for the C and various config/IO/etc files). All are named according to their functionality so its easy to drill down to what I need to explore.

    This is code and project organization and is independent of the chip/language/application etc. But its just my way of doing it. Surely others will chime in and you can pick and choose the best parts and sort it out for yourself.

    Have fun!
    Last edited: Nov 30, 2012
    aamirali likes this.
  7. aamirali

    Thread Starter Member

    Feb 2, 2012
    Hi John,

    Thanks for good reply.
  8. aamirali

    Thread Starter Member

    Feb 2, 2012
    Hi John,

    Can you also show me one such example code project having large source files
    Last edited: Dec 12, 2012
  9. JohnInTX


    Jun 26, 2012
    Sorry, but virtually every large project I've written has been for various clients who would not like their IP posted on the intertubes. If you have specific questions about some of your code, post it and we can go from there.

    For starters:
    Collect all of your I/O definitions into one header file (IODEF.h) Moving from 48 to 64 pins means TWO files (IODEF48.h and IODEF64.h). In each file is a complete list of the pins on each port. Here is a sample. In this particular case, the IODEFxx files resolve differences in PCB revisions but the same idea applies - different I/O map = different IODEFxx.h file...

    Code ( (Unknown Language)):
    1. //***************** IODEF_Rev1.h *******************
    3. //----------------------  PORT A  -------------------------------
    5. #if 0
    6.   DIR    INIT     PIN    FUNCTION
    7. ------------------------------------------------
    8. // PORT A: Analog, RA6 muxed out of OSC2
    10.   DIR INIT    PIN    FUNCTION
    11. ------------------------------------------------
    12. RA6    O    1    50  Motor STEP or CW Step
    13. RA5    O    0    33    uncommitted
    14. RA4 O    0    34    uncommitted
    15. RA3    I    1     27    ANALOG_IN: 0-10V aux in
    16. RA2    I    1    28    ANALOG_ECHO: Realtime echo - analog
    17. RA1    I    1    29    ANALOG2_MON: Monitors aux analog output
    18. RA0    I    1    30    PPC_MON: Monitors PPC analog output
    19. #endif
    21. #define TRISAinit 0x0f
    22. #define LATAinit  0x4f
    24. #define MOTOR_STEP_1 (LATA |= 0x40)
    25. #define MOTOR_STEP_0 (LATA &= ~0x40)
    26. #define MOTOR_STEP_OUT_IS_1 (LATA & 0x40)
    29. //----------------------  PORT B  -------------------------------
    30. #if 0
    31.   DIR INIT     PIN    FUNCTION
    32. ------------------------------------------------
    33. RB7 O    0    47    dedicated to ISCP, set as outputs
    34. RB6    O    0    52
    35. RB5    O    0    53
    36. RB4 O    1   54  1=MOTOR_SHUTDOWN
    37. RB3    O    0     55    
    38. RB2    O    0    56    
    39. RB1    O    1    57    Motor DIRECTION or CCW Step
    40. RB0    I    1    58    IRQ:CAN1_IRQ
    41. #endif
    43. #define TRISBinit 0x01
    44. #define LATBinit  0x13
    46. #define SHUTDOWN_MOTOR (LATB |= 0x10)
    47. #define RUN_MOTOR (LATB &= ~0x10)
    49. #define MOTOR_DRIVE_LEFT (LATB |= 0x02)        // 1 = CW rotation
    50. #define DRIVING_LEFT (LATB & 0x02)             // true when moving in left hand direction
    52. #define MOTOR_DRIVE_RIGHT (LATB &= ~0x02)    // 0 = CCW rotation
    53. #define DRIVING_RIGHT (~LATB & 0x02)         // true when moving in right hand direction
    55. #define MOTOR_TOGGLE_DIR (LATB ^= 0x02)
    Notice how each port/pin gets defined by its function in the code. For example SHUTDOWN_MOTOR and RUN_MOTOR are macros that flip port bits. In the code flow, I use RUN_MOTOR instead of specific port access. Now, I can change the chip, I/O assignment etc. just by editing the IODEF file or replacing it altogether. If RUN_MOTOR gets more involved, change the macro to a function call - but do it here in the IODEF file, not in the main flow i.e.
    Code ( (Unknown Language)):
    1. #define RUN_MOTOR runMotor()
    The higher level code never knows the difference so you never have to touch it, even though the I/O assignments change or a different motor setup entirely is used. As a rule of thumb, edit IODEF when a change is permanent and not back-supported, have multiple IODEF files when you have to support multiple configurations (PCB revisions etc.)

    Now what? Well, now include the I/O definitions but.. since we now have two IODEFxx.h files, how to do that? I use a master IODEF.h file that includes a selected IODEFxx.h file according to some defines...
    Code ( (Unknown Language)):
    2. //*************** IODEF.h ****************
    4. #if (PCB_REV == 1)
    5. #include "IODDEF_Rev1.h"
    6. #endif
    8. #if (PCB_REV == 2)
    9. #include "IODEF_Rev2.h"
    10. #endif
    12. // The compiler is happy to check my work..
    13. #if    !((PCB_REV == 1) || (PCB_REV == 2))
    14. #error PCB Rev MUST be 1 or 2!!!
    15. #endif
    17. //--------------------------  PROCESSOR  ------------------------------
    18. // Pre Rev 2 boards use 8720, 8722 after that.
    20. #ifdef _18F8720
    21. #define PROCESSOR "18F8720"
    22. #define _18Fxx20
    23. #endif
    25. #ifdef _18F8722
    26. #define PROCESSOR "18F8722"
    27. #define _18Fxx22
    28. #endif
    30. //Again, the compiler is used to make sure I haven't done something stupid..  at least here..
    31. #if (PCB_REV == 1)
    32. #ifndef _18F8720
    33. #error "Rev 1 PCB needs 18F8720"
    34. #endif
    35. #endif
    So now, we can specify either of two PCB revisions which have different I/O maps, different number of pins etc. and again, the main code is not affected, it just invokes RUN_MOTOR like it always did and its happy. Note also in this one, PCB rev 1 and 2 use different processors with enough differences (errata in this case, I think..) to require some different code somewhere so PROCESSOR is defined. Note that the compiler passes in a command line parameter e.g._18F8722 but these sometimes change with the compiler versions so here I figure it out and re-define it in a consistent way. Never rely on command-line params. But we are not done... where does PCB_REV come from... CONFIG.h of course!

    Code ( (Unknown Language)):
    1. //***************  CONFG.H **************
    3. // This file contains all config info for <your project>
    5. //---------------------------  PROJECT  -------------------------------
    7. #define BUILD_FOR_RELEASE   // define for release code, comment out for debug
    9. //------------------------  VERSION  ---------------------------------
    10. // This stuff is common for debug and release versions..
    11. #define PRODUCT "SPIFFY PRODUCT"
    12. #define VERSION "2.06-2b"
    13. #define PCB_REV 2            // Major rev of pcb
    15.  #ifdef BUILD_FOR_RELEASE  // then include hard-coded setups ..
    16. #include "CONFIG_RELEASE.h"  // this file has all options permanently set for RELEASE versions..
    18.   #else  // build for debug..
    20. #MESSG "CONFIG.h: Building for DEBUG"  // show what's happening in build window
    22. //---------------------- EMULATOR  ---------------
    23. #define SLOW_EMULATOR 0  // forces message in signon screen
    24. #define USE_25MHZ 1          // for 8720 at 25MHz for rev 2
    26. //--------------------  OSCILLATOR  ---------------------------
    28. // ICE 2000 won't emulate at full 40mhz
    30. #if SLOW_EMULATOR || USE_25MHZ
    31. #define SYSFREQ_25MHZ
    32. #define Fosc 25.0E6
    33. #else
    34. #define SYSFREQ_32MHZ
    35. #define Fosc 32.0E6                // for calculations
    36. #endif
    38. #endif // !BUILD_FOR_RELEASE
    40. //------------------------  STANDARD INCLUDE FILES  ------------------
    42. #include "types.h" // all derived types here
    43. #include "glbls.h"  // all global variables here
    44. #include "IODEF.h" // the master IODEF file that includes the correct one for the build
    CONFIG.h is the last file you visit before building so its here that the final build configuration is done. Here we have a couple of things going on..
    PCB_REV is defined for this build. That in turn includes the correct I/O map and verifies the processor..

    Some debug stuff is defined. In this one, an emulator would not run at full speed so for debug, the system clock is slower. All of the timer settings are calculated of the Fosc setting so it only has to be changed here.

    BUILD_FOR_RELEASE is switched. Note that when defined, it includes a different CONFIG file. CONFIG_RELEASE.h has all of the same defines but they are permanently set for a real released version i.e. all debug code is removed, Fosc set correctly etc... That way, you don't have to remember to reset everything AND you don't have to wade through your other source looking for test code etc.

    Finally, all standard .h file are included here so that each .c source only has to include CONFIG.h and not worry about debug settings, emulators, release/debug etc.

    Note the MESSG directive. In PICC, this outputs the message to the build window when building. I look at these to make sure I've got everything set up like I want.

    Note that so far none of this has generated ANY code. It just describes your system to the higher level program flow and controls how it gets built.

    Hope this helps. The key is to get as much hardware/version/config defined stuff out of your code flow so that it may be maintained separately. The main code should just describe the problem you are solving with your program at a high level and not worry about the details.

    BTW: the source was cut and pasted from a couple of projects. If you find an unmatched #endif etc. it would not be unexpected.
    Last edited: Dec 13, 2012
  10. aamirali

    Thread Starter Member

    Feb 2, 2012
    Thnaks JohnInTX,

    I ahve applied the tips & also now just by changing one variable my code automatically put 48 or 64 pin defs.

    Help me in below problem also:

    1.In large number of files, there are large no of functions which are being shared between each file.
    Suppose at any point of time, one of parameter of functios where it is defined, changed from uint8_t to uint16_t .
    Then compiler don't generate warning that in other files the parameter is still declared as uint8_t. & in those files if 300 is passed as argument. Those will change it to 255 .

    How to deal with this kind of problem, so that function declarations remain consistent.

    2. Similarly same thing for variables. Compiler don't generate warning. how to make sure that defination & declaration remain consistent always, even if erroro occur compiler generate warning
  11. JohnInTX


    Jun 26, 2012
    If I understand what you need..

    1) If you declare your functions using a parameter list in parenthesis the compiler or linker will alert you to some problems of using the wrong type, pointers for example. But ints, char etc will get cast to the parameter type, usually with no warning.

    Code ( (Unknown Language)):
    1. void MyFunction(int x); // function prototype in a .h file for all to see
    3. void MyFunction (int x)  // in a .c file to write the function
    4. {
    5.  }
    7. void MyFunction(x)  // don't write it like this.
    8.  int x;
    9. {
    10. }
    Using prototypes and parameter lists in the declarations allows SOME compilers/linkers to check the parameters and throw a warning if you pass it something other than declared. Keil and XC8 appear to just promote params to the declared type. Even setting Strict ANSI does not flag an int passed to a char parameter. It WILL check for pointer problems i.e. passing an int when and int* is called for.

    I tried a few examples and confirmed that Keil Cortex M3 and Microchip's XC8 is happy to take just about any mix of char/int types but XC8 at least threw a warning when using a float as a parameter to an int.

    Lint should find it although I didn't use it in the examples.

    The other problem of changing the type of a function's parameter can be helped by using typedef. For example:
    Code ( (Unknown Language)):
    1. typedef char FUCNT_PARAM_TYPE;  // declare MyFunction's paramter type as char.
    3. FUNCT_PARAM_TYPE x,y,z;  // declare 3 vars x,y and z of type char
    5. void MyFunction(FUNCT_PARAM_TYPE x); // x is whatever the typedef says it is
    Later, if you decide you need to go to an int you can change the parameter type to:

    Code ( (Unknown Language)):
    1. typedef [B]int[/B] FUCNT_PARAM_TYPE;  // declare MyFunction's paramter type as int.
    When the project is rebuilt, all variables/parameters of FUNCT_PARAM_TYPE will be changed to int and all should play OK. Note that for good programming practice, ALL parameters passed to MyFunction should be the derived type. Don't cheat and make some explicitly a char and some the derived type.

    Note that when you change the typedef, you must rebuild the entire project.

    2) As stated, C is a little loose about parameter checking, at least in the (older) ANSI standard I have. The fact that the latest XC8 compiler is still happy to convert parameter types as needed, even when Strict ANSI is selected, seems to show that its still that way. The compiler/linker might provide a switch to tighten up parameter checking.

    See error E051 in Keil's help files. That looks like what you want but I'm not familiar enough with Kiel (yet) to know how to use it.

    BTW Be sure never to suppress or ignore compiler/linker warnings. Your code is not ready to run unless it builds with 0 Errors 0 Warnings. Check the help files for 'Type Checking', 'Function prototypes', Formal and Actual Parameters' etc. to see to parameter type checking is supported.

    'Lint' would flag parameter type conversions but it can be a pain to use.
    Last edited: Feb 6, 2013
    aamirali likes this.