diff --git a/Makefile.base b/Makefile.base
index fd59c084d1e00f4618f27560d5099bd5f7bbe1f7..98f75964a376b50700227d4f62a6e199a4b934af 100644
--- a/Makefile.base
+++ b/Makefile.base
@@ -56,13 +56,13 @@ CXXFLAGS = $(filter-out $(CXXUWFLAGS), $(CFLAGS)) $(CXXEXFLAGS)
 
 # compile and generate dependency info
 
-$(OBJC): $(BINDIR)$(MODULE)/%.o: %.c
+$(OBJC): $(BINDIR)$(MODULE)/%.o: %.c $(RIOTBUILD_CONFIG_HEADER_C)
 	$(AD)$(CCACHE) $(CC) \
 		-DRIOT_FILE_RELATIVE=\"$(patsubst $(RIOTBASE)/%,%,$(abspath $<))\" \
 		-DRIOT_FILE_NOPATH=\"$(notdir $<)\" \
 		$(CFLAGS) $(INCLUDES) -MD -MP -c -o $@ $(abspath $<)
 
-$(OBJCXX): $(BINDIR)$(MODULE)/%.o: %.cpp
+$(OBJCXX): $(BINDIR)$(MODULE)/%.o: %.cpp $(RIOTBUILD_CONFIG_HEADER_C)
 	$(AD)$(CCACHE) $(CXX) \
 		-DRIOT_FILE_RELATIVE=\"$(patsubst $(RIOTBASE)/%,%,$(abspath $<))\" \
 		-DRIOT_FILE_NOPATH=\"$(notdir $<)\" \
diff --git a/Makefile.include b/Makefile.include
index 3ef25e82885e7dfc30123864696c56eab24aead1..056dc8dea4514a2e0590fe927acdaf9e219995c0 100644
--- a/Makefile.include
+++ b/Makefile.include
@@ -50,6 +50,8 @@ include $(RIOTBASE)/Makefile.docker
 # Static code analysis tools provided by LLVM
 include $(RIOTBASE)/Makefile.scan-build
 
+export RIOTBUILD_CONFIG_HEADER_C = $(BINDIR)riotbuild/riotbuild.h
+
 COLOR_GREEN  :=
 COLOR_RED    :=
 COLOR_PURPLE :=
@@ -226,12 +228,6 @@ ifeq ($(origin RIOT_VERSION), undefined)
   endif
 endif
 
-ifneq (,$(RIOT_VERSION_OVERRIDE))
-export CFLAGS += -DRIOT_VERSION=\"$(RIOT_VERSION_OVERRIDE)\"
-else
-export CFLAGS += -DRIOT_VERSION=\"$(RIOT_VERSION)\"
-endif
-
 # the binaries to link
 BASELIBS += $(BINDIR)${APPLICATION}.a
 BASELIBS += $(USEPKG:%=${BINDIR}%.a)
@@ -255,7 +251,7 @@ ifeq ($(BUILD_IN_DOCKER),1)
 all: ..in-docker-container
 else
 ## make script for your application. Build RIOT-base here!
-all: ..compiler-check ..build-message $(USEPKG:%=${BINDIR}%.a) $(APPDEPS)
+all: ..compiler-check ..build-message $(RIOTBUILD_CONFIG_HEADER_C) $(USEPKG:%=${BINDIR}%.a) $(APPDEPS)
 	$(AD)DIRS="$(DIRS)" "$(MAKE)" -C $(APPDIR) -f $(RIOTBASE)/Makefile.application
 ifeq (,$(RIOTNOLINK))
 ifeq ($(BUILDOSXNATIVE),1)
@@ -286,7 +282,7 @@ include $(RIOTBASE)/drivers/Makefile.include
 
 # The `clean` needs to be serialized before everything else.
 ifneq (, $(filter clean, $(MAKECMDGOALS)))
-    all $(BASELIBS) $(USEPKG:%=$(RIOTPKG)/%/Makefile.include): clean
+    all $(BASELIBS) $(USEPKG:%=$(RIOTPKG)/%/Makefile.include) $(RIOTBUILD_CONFIG_HEADER_C): clean
 endif
 
 # include Makefile.includes for packages in $(USEPKG)
@@ -301,7 +297,7 @@ USEMODULE_INCLUDES_ = $(shell echo $(USEMODULE_INCLUDES) | tr ' ' '\n' | awk '!a
 INCLUDES += $(USEMODULE_INCLUDES_:%=-I%)
 
 .PHONY: $(USEPKG:%=${BINDIR}%.a)
-$(USEPKG:%=${BINDIR}%.a):
+$(USEPKG:%=${BINDIR}%.a): $(RIOTBUILD_CONFIG_HEADER_C)
 	@mkdir -p ${BINDIR}
 	"$(MAKE)" -C $(RIOTPKG)/$(patsubst ${BINDIR}%.a,%,$@)
 
@@ -380,7 +376,7 @@ eclipsesym: $(CURDIR)/eclipsesym.xml
 eclipsesym.xml: $(CURDIR)/eclipsesym.xml
 
 $(CURDIR)/eclipsesym.xml:
-	$(AD)printf "%s\n" $(CC) $(CFLAGS) $(INCLUDES) | \
+	$(AD)printf "%s\n" $(CC) $(CFLAGS_WITH_MACROS) $(INCLUDES) | \
 		$(RIOTBASE)/dist/tools/eclipsesym/cmdline2xml.sh > $@
 
 # Extra make goals for testing and comparing changes.
@@ -495,3 +491,22 @@ include $(RIOTBASE)/dist/tools/desvirt/Makefile.desvirt
 
 # include bindist target
 include $(RIOTBASE)/Makefile.bindist
+
+# Build a header file with all common macro definitions and undefinitions
+# make it phony to force re-run of the script every time even if the file exists
+# The script will only touch the file if anything has changed since last time.
+.PHONY: $(RIOTBUILD_CONFIG_HEADER_C)
+$(RIOTBUILD_CONFIG_HEADER_C):
+	@mkdir -p '$(dir $@)'
+	$(AD)'$(RIOTBASE)/dist/tools/genconfigheader/genconfigheader.sh' '$@' $(CFLAGS_WITH_MACROS)
+
+CFLAGS_WITH_MACROS := $(CFLAGS)
+CFLAGS := $(patsubst -D%,,$(CFLAGS))
+CFLAGS := $(patsubst -U%,,$(CFLAGS))
+CFLAGS += -include '$(RIOTBUILD_CONFIG_HEADER_C)'
+
+ifneq (,$(RIOT_VERSION_OVERRIDE))
+export CFLAGS += -DRIOT_VERSION=\"$(RIOT_VERSION_OVERRIDE)\"
+else
+export CFLAGS += -DRIOT_VERSION=\"$(RIOT_VERSION)\"
+endif
diff --git a/Makefile.modules b/Makefile.modules
index 37bef54fde0fa0405b0c8262f529eea66d791265..b6ec9d3428dc4afcc150fe9830d98a1999a56f5e 100644
--- a/Makefile.modules
+++ b/Makefile.modules
@@ -6,9 +6,9 @@ USEMODULE := $(filter-out $(filter-out $(FEATURES_PROVIDED), $(FEATURES_OPTIONAL
 INCLUDES += -I$(RIOTBASE)/core/include -I$(RIOTBASE)/drivers/include -I$(RIOTBASE)/sys/include
 INCLUDES += -I$(RIOTCPU)/$(CPU)/include
 INCLUDES += -I$(RIOTBOARD)/$(BOARD)/include
-ED = $(patsubst %,-DMODULE_%,$(subst -,_,$(USEMODULE) $(USEPKG)))
-ED += $(patsubst %,-DFEATURE_%,$(subst -,_,$(filter $(FEATURES_PROVIDED), $(FEATURES_REQUIRED))))
-EXTDEFINES = $(shell echo $(sort $(ED))|tr 'a-z' 'A-Z')
+ED = $(addprefix FEATURE_,$(sort $(filter $(FEATURES_PROVIDED), $(FEATURES_REQUIRED))))
+ED += $(addprefix MODULE_,$(sort $(USEMODULE) $(USEPKG)))
+EXTDEFINES = $(addprefix -D,$(shell echo '$(ED)' | tr 'a-z-' 'A-Z_'))
 REALMODULES = $(filter-out $(PSEUDOMODULES), $(sort $(USEMODULE)))
 export BASELIBS += $(REALMODULES:%=$(BINDIR)%.a)
 
diff --git a/dist/tools/genconfigheader/README.md b/dist/tools/genconfigheader/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..07321a007d7324f11800ed9c54481b9eba3be556
--- /dev/null
+++ b/dist/tools/genconfigheader/README.md
@@ -0,0 +1,14 @@
+genconfigheader.sh
+------------------
+
+Usage: `genconfigheader.sh <output_header_file> [CFLAGS]`
+
+Generate a build configuration header from CFLAGS arguments
+
+ - Arguments on the form `-Dmacro_name=macro_value` will be converted to
+   the form `#define macro_name macro_value`
+ - Arguments given on the form `-Dmacro_name` will be converted to the form `#define macro_name 1`
+ - The output file will be overwritten if it already exists _and_ the new output file's contents differs from the old file.
+
+By not replacing the output file on every run, make can still use the file
+modification times for dependency calculations.
diff --git a/dist/tools/genconfigheader/genconfigheader.sh b/dist/tools/genconfigheader/genconfigheader.sh
new file mode 100755
index 0000000000000000000000000000000000000000..901948c6afe9512430acd0f2c15e6aad4b06a157
--- /dev/null
+++ b/dist/tools/genconfigheader/genconfigheader.sh
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+#
+# Copyright (C) 2016 Eistec AB
+#
+# This file is subject to the terms and conditions of the GNU Lesser General
+# Public License v2.1. See the file LICENSE in the top level directory for more
+# details.
+#
+
+DEBUG=0
+if [ "${QUIET}" != "1" ]; then
+  DEBUG=1
+fi
+
+if [ $# -lt 1 ]; then
+  echo "Usage: $0 <output.h> [CFLAGS]..."
+  echo "Extract all macros from CFLAGS and generate a header file"
+  exit 1
+fi
+OUTPUTFILE="$1"
+shift
+
+MD5SUM=md5sum
+if [ "$(uname -s)" = "Darwin" ]; then
+  MD5SUM=md5 -r
+fi
+
+# atomically update the file
+TMPFILE=
+trap '[ -n "${TMPFILE}" ] && rm -f "${TMPFILE}"' EXIT
+# Create temporary output file
+TMPFILE=$(mktemp ${OUTPUTFILE}.XXXXXX)
+
+if [ -z "${TMPFILE}" ]; then
+  echo "Error creating temporary file, aborting"
+  exit 1
+fi
+
+# exit on any errors below this line
+set -e
+
+echo "/* DO NOT edit this file, your changes will be overwritten and won't take any effect! */" > "${TMPFILE}"
+echo "/* Generated from CFLAGS: $@ */" >> "${TMPFILE}"
+for arg in "$@"; do
+  case ${arg} in
+    -D*)
+      # Strip leading -D
+      d=${arg#-D}
+      if [ -z "${d##*=*}" ]; then
+        # key=value pairs
+        key=${d%%=*}
+        value=${d#*=}
+        echo "#define $key $value" >> "${TMPFILE}"
+      else
+        # simple #define
+        echo "#define $d 1" >> "${TMPFILE}"
+      fi
+      ;;
+    -U*)
+      # Strip leading -U
+      d=${arg#-U}
+      echo "#undef $d" >> "${TMPFILE}"
+      ;;
+    *)
+      continue
+      ;;
+  esac
+done
+
+# Only replace old file if the new file differs. This allows make to check the
+# date of the config header for dependency calculations.
+NEWMD5=$(${MD5SUM} ${TMPFILE} | cut -c -32)
+OLDMD5=$(${MD5SUM} ${OUTPUTFILE} 2>/dev/null | cut -c -32)
+if [ "${NEWMD5}" != "${OLDMD5}" ]; then
+  if [ "${DEBUG}" -eq 1 ]; then echo "Replacing ${OUTPUTFILE} (${NEWMD5} != ${OLDMD5})"; fi
+  # Set mode according to umask
+  chmod +rw "${TMPFILE}"
+  mv -f "${TMPFILE}" "${OUTPUTFILE}"
+else
+  if [ "${DEBUG}" -eq 1 ]; then echo "Keeping old ${OUTPUTFILE}"; fi
+fi
+
+# $TMPFILE will be deleted by the EXIT trap above if it still exists when we exit