how to use .h & .c in arduino projects? (and other, semi-n00b questions.)

Hello everybody!

Im trying to learn to program c, been going at it for about 6 years now.
Im creating a project in order to learn in a hands-on approach and im making things overly complicated, but hopefully a better structured project for easier long-term development.

So i got a bunch of stuff that needs to be going on, and ive been thinking "state machine" while im laying up the basic systems structure (if "millis elapsed>defVal"{ runFunc;} ) and i figured i should export the big chunks of program code into separate files, to simplify human readability and so on.

This leads to the main question, how do i implement the .h and .c files into a arduino project?

im pretty confident in using .h header files, generally using it to store data structures and general "runtime" functions, but i have no clue how to actually use the .c files.

ive been reading the examples ive found thru search engines, as well as the reference database here on arduino.cc, so i got what they´re saying, but i still don´t understand how to use a .c file.. :frowning:

As far as i can tell, the actual .c file isn't actually #included or in other way linked to the project .ino, its just there, containing code..?
And this appears to work for other projects, as long as the .h and the corresponding .c file share the filename, thisFile.h, thisFile.c.

So could anyone please explain this so my borderline autistic mind can absorb it? Its just not making sense to me right now..

My current way of doing things is to put all data (structures and such) in a separate header, then include this into all the different headers that needs access to the global data.
i use the same approaches for functions, big ones are kept in their own header files, then called from main loop() when their timer have elapsed since last run.

Would anyone argue that this approach isnt ok or have a better suggestion?

Also, what should i be keeping in the .h file and what is generally stored in the .c file?
I'm not writing any classes and so on yet, its mainly data structures and modifying functions called on a time-elapsed (millis) counter from void loop() and responses to user input (buttons ´n stuff).

In general, h files contain what other files need to know about. E.g. a variable or a function prototype.

E.g. in a project I'm working on, I have one of those LCD shields with 5 buttons that are connected on an analog pin. I created a h file and include that in the files that need to be able to read those buttons

buttons.h

#ifndef BUTTON_H
#define BUTTON_H

// possible buttons
enum BUTTON {BTN_NONE, BTN_SELECT, BTN_LEFT, BTN_RIGHT, BTN_UP, BTN_DOWN};
// read buttons and return which button was pressed
BUTTON readButtons();

#endif

So when compiling another file, the the compiler knows what the function readButtons() looks like; it does not take an argument and can return one of the 6 values of the enum.

buttons.cpp

#define NUMELEMENTS(x) sizeof(x)/sizeof(x[0])

#include "buttons.h"
#include "io.h"

// analog value +/- 40 to determine which button is pressed
const int boundary = 40;

// lookup table entry
struct BTN
{
  BUTTON btn;
  int val;
};

// lookup table to translate analog value to BUTTTON
BTN buttons[] =
{
  {BTN_SELECT, 638},
  {BTN_LEFT, 407},
  {BTN_RIGHT, 0},
  {BTN_UP, 98},
  {BTN_DOWN, 255},
};

// debounce delay
const unsigned int debounceDelay = 25;

/*
  convert analog reading to button
  IN:
    analog value
  Returns:
    detected button
*/
BUTTON lookup(int val)
{
  for (unsigned int cnt = 0; cnt < NUMELEMENTS(buttons); cnt++)
  {
    if (val >= buttons[cnt].val - boundary && val <= buttons[cnt].val + boundary)
    {
      return buttons[cnt].btn;
    }
  }
  return BTN_NONE;
}

/*
  read the buttons on specified pin; implements debouncing
  Returns:
    BTN_NONE if no button was pressed, else value for pressed button
*/
BUTTON readButtons()
{
  static BUTTON lastReading = BTN_NONE;
  static BUTTON stableButton = BTN_NONE;


  static unsigned long debounceStartTime;

  int val = analogRead(pinButtons);
  BUTTON reading = lookup(val);

  // if button is bouncing
  if (reading != lastReading)
  {
    debounceStartTime = millis();
    lastReading = reading;

    return stableButton;
  }

  if (millis() - debounceStartTime >= debounceDelay)
  {
    if (stableButton != reading)
    {
      lastReading = reading;
      stableButton = reading;
    }
  }

  return stableButton;
}

readButtons uses a function lookup(). This function is not declared in buttons.h and hence the compiler does not know about it when compiling other files that want to use the function; if you try to use it in the sketch, the compiler will complain. The same applies to the variables and the struct.

The cpp includes two files. It needs to know about the enum (hence buttons.h) and io.h contains the pin number (I have centralised all pin numbers in io.h).

xarvox:
As far as i can tell, the actual .c file isn't actually #included or in other way linked to the project .ino, its just there, containing code..?

All the source files in the sketch folder or folders of libraries containing a file you #included will automatically be compiled.

xarvox:
as long as the .h and the corresponding .c file share the filename, thisFile.h, thisFile.c.

That's not necessary but it does make sense to match the filenames just to keep things organized.

Keep in mind that .ino files are actually C++ so if you want to use a function defined in a .c file in a .ino file you need to wrap the prototype in extern "C" {}.

xarvox:
and i figured i should export the big chunks of program code into separate files, to simplify human readability and so on.

This leads to the main question, how do i implement the .h and .c files into a arduino project?

For my projects I just put everything into the .h file and I don't bother with .c or .cpp files.

I'm sure that would be frowned on by C/C++ experts. But it seems to work fine.

...R

Thank you all for your input here!
Sorry about the late reply, my notifications settings were not set correctly..

Its getting a bit clearer to me, i only store the data and function prototypes in .h, the actual function declarations (bulk content) go into a .c or .cpp file.

The actual file names doesn't matter, i could use a "file.h", containing two function prototypes, then have "file_sub1.c" containing one of the function declarations and put the other one in "file_sub2.c"?

The important part is to link each .c file to its "parent" .h file with a #include "parent.h" statement at the top of each .c file, and #include "parent.h" from the main sketch, is this correct?

Im already using include guards in all my headers, but i understand i wont be needing those for the .c files, since they´re only included once by the compiler?

Again, thank you folks for your time and tutoring here, its much appreciated!
/H

xarvox:
The actual file names doesn't matter, i could use a "file.h", containing two function prototypes, then have "file_sub1.c" containing one of the function declarations and put the other one in "file_sub2.c"?

Yes.

xarvox:
The important part is to link each .c file to its "parent" .h file with a #include "parent.h" statement at the top of each .c file, and #include "parent.h" from the main sketch, is this correct?

There is no such thing as linking :wink: You do not need to include parent.h in parent.cpp; that is only necessary if parent.cpp needs to know something that is defined/declared in parent.h. In the example that I gave, that is the enum.

The Arduino build process will compile the ino file and any c/cpp file that it finds in the sketch directory and a number of other locations. Even if the functions in those files are not used, they will be compiled; the linker however will throw them away in that case.

But when compiling one of those ino/cpp files and they make use of functions, variables etc. in another somefile.cpp, you need to include the file that somefile.h in those ino/cpp files so the compiler knows what the functions look like (which arguments they take and what they return so it can warn you (warning or error) if you make mistakes (e.g. passing a float to a function that e.g. wants a character array.

Including somefile.h in the main sketch where the the functions from somefile.cpp are not used and not in yetanotherfile.cpp where the functions are actually used will result in errors.

Thank you very much for clearing this up for me, i now understand!

Im implementing this newly aquired knowledge into my project and i´ll make sure not to include things where not needed. :slight_smile:

I gather that i could, in theory, have the "void loop() {/mainloop/}" in a separate "loop.c" that isnt actually mentioned in the ino-file or any other #include´d header files.

It certainly clears up all the question marks ive been having about how libraries are working, thank you yet again for your tutoring and patience!