Minimal Makefile for Arduino Projects

Hello there,

I'm looking for the most simple Makefile to build a normal Arduino project, let's say "Blink". The Makefile should compile the Arduino core modules and the sketch and then create the HEX file. For simplicity, let's assume that all of the source files are located in the current directory. Also, there is a main.c file which contains the actual entry point and no further pre-processing needs to be done (like adding #include statements).

I know there are multiple Makefiles out there, but they either 1) do not work, or 2) have way too many config options, or 3) have external dependencies. I only need a minimal example which I can use as a template for other projects. No arduino-builder, no complicated swiss-knife makefile, no external dependencies. Just make.

Why do I need this? I would like to do different builds for testing (on the host machine) as well as production (the microcontroller target). This means I have to replace some of the production code by test doubles at link time. The Arduino IDE does not provide this functionality and I would like to have more control over the build process.

It’s not as simple as you seem to think.
(More later)

Is there any reason not to use the Arduino IDE from the command line?

...R

How about make without makefiles? Here is one of my projects: GitHub - mikaelpatel/Arduino-Makefile: Makefile-less extension of Arduino-Makefile

Cheers!

Is there any reason not to use the Arduino IDE from the command line?

I don't think it gives you the features that the OP was hoping to get from Make. Though I'm not sure what those are, exactly...

For simplicity, let's assume that all of the source files are located in the current directory.

Also, there is a main.c file which contains the actual entry point and no further pre-processing needs to be done

Neither of which is the normal case. Source is scattered across at least three directories, and possibly many more (when core libraries, arduino libraries, and user-installed libraries are all in use.) And there is complicated pre-processing that figures out exactly which include paths to used based on simple #include statements, board type, and ... searching of those many library locations...

Here's how the suggestion looks like in detail:
The Arduino IDE creates a scratch directory and copies the core modules and libraries into that directory

That doesn't sound very efficient. It's on the order of 100 files...

The Arduino IDE performs additional preparation steps such as adding #include statements and creating the actual main.c file based on the scratch

main.c already exists. The #include statements already exist. What the pre-processing does is compute paths for the compile command ("-Ixxxx") and prototypes for user functions in the .ino files.

The Arduino IDE generates a Makefile which builds all of the source files (core, libraries, sketch) and the HEX file. The Makefile should have two targets: "compile" and "program". Those targets are linked to the buttons "verify" and "upload" in the IDE.

Bah. If you have to generate a Makfile every time you do a build, it's definitely not worth it!

[existing makefiles] have way too many config options

This is something that tends to be true of Makefiles in general. :frowning:

Why do I need this? I would like to do different builds for testing (on the host machine) as well as production (the microcontroller target).

Ah. "IOU" for Arduino. I've considered that a couple of times...
So in fact the changes you are suggesting won't even fulfill your requirement - they'll just get you closer to something that you can easily modify...

I THINK what you're trying to do could be done relatively readily just by creating a new "platform", which is documented... (see Creating Custom Boards for Board Manager - IDE 1.x - Arduino Forum ) Much more easily that going to a make-based system, anyway.
(Well, you could could have Make run the "arduino cli", but that probably doesn't do what you want.)

westfw:
Though I'm not sure what those are, exactly...

That's why I thought it was worth suggesting the obvious. :slight_smile:

...R

westfw:
It’s not as simple as you seem to think.

You're absolutely right. I'm chewing on this for a while now. But then again, the Arduino core library is just C code built around the AVR target. So it should be possible to build the core library without the Arduino IDE.

I think I need to elaborate a bit more. My main motivation behind this is to do thorough testing of my software modules. I know that there are multiple ways of testing embedded software such as 1) stepping through the code with a debugger, 2) manual testing with lab equipment, or 3) offline testing on the host machine. I want to do 3). I'll give you an example. Suppose you have a module called "SensorDriver" which encapsulates the operation of a particular sensor. The sensor uses only digital pins. The code of the SensorDriver only interacts with the hardware through pinMode/digitalRead/digitalWrite and nothing else. There is a well-defined boundary between the SensorDriver and the actual hardware. When you have implemented the SensorDriver you want to test it. Using 1) and 2) you can obviously only test on the target hardware. This has many limitations. Then there's 3). You cut out your software module and test it on the host machine. All you have to do is to fake the inputs of the software module and compare the actual outputs with the expected outputs. In order to fake the inputs you have to replace the actual hardware access by a stub. This is easy since we have a well-defined interface between the driver and the hardware. The only thing we need to do is to replace the interface by a test double on the host machine. This can be done either at link-time using a fake implementation of the pinMode/digitalRead/digitalWrite or at run-time using function pointers (in reality I have a "cleaner" interface for that). The code of the SensorDriver can be fully tested on the host machine and need not be changed. The SensorDriver uses ANSI C and stdint data types to give (approximately) the same behavior on the embedded target and on the host machine. Now I need two builds for the same production code: i) a release build for the embedded target, and ii) a test build which includes all of the test cases for the host target. I can do i) with the Arduino IDE, but not ii) and more importantly, not i) and ii) combined.

The technique outlined above is not new or unconventional for embedded software. I have the impression that it is more popular among Java developers but it is perfectly feasible for embedded software as well. All you need is a hardware abstraction layer. It might seem like a lot of effort at first, but it's not and you get a lot of benefits from that.

I want to do proof-of-concept for that. That's why I have made so many simplifications (all source files are in one location, no auto-detection of libraries, etc.). An additional boundary condition would be that the used core modules are mostly fixed during development time, i.e. a manual configuration is perfectly fine for me. Once the proof-of-concept is running I can still extend the Makefile from there.

In the meantime, I have made some progress on this issue. I have created a Makefile with the help of the Arduino IDE's verbose build output. The Makefile builds the core libraries and the executable. However, the executable was significantly bigger than the one produced by the Arduino IDE. While tidying up the Makefile to handle dependencies, I decided it's not worth it to maintain a Makefile by hand and I switched over to CMake. I have to say, this is truly a glorious tool. I did only set the source files and the toolchain and I had a working build in no time. Plus, all the dependencies are handled by CMake as well. Now I also have an executable but it's still bigger that the one produced by the Arduino IDE. I need to check what went wrong here ...

All you need is a hardware abstraction layer.

Don't forget to change "int" to 16bits and "double" to 32bits... (the usual bottleneck in my efforts along similar paths.)

the executable was significantly bigger than the one produced by the Arduino IDE.

Probably caused by either lack of "-ffunction-sections -fdata-sections" in the compile and "-gc-sections" in the link (which cause unused code to be omitted), or by not using "-flto" (link-time optimization) everywhere.