diff --git a/node_board/CMakeLists.txt b/node_board/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b7ba219bf484f39eafc33f74f30b63b8a774c56d
--- /dev/null
+++ b/node_board/CMakeLists.txt
@@ -0,0 +1,19 @@
+cmake_minimum_required(VERSION 3.13) # 3.13 is required for target_link_options
+project(NodeBoard CXX)
+
+if(NOT CMAKE_BUILD_TYPE)
+    set(CMAKE_BUILD_TYPE "Release")
+endif()
+message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
+
+include(cmake/shared_settings.cmake)
+find_package(PNG REQUIRED)
+
+add_executable(generate_node_board
+    main.cpp
+    png_writer.cpp
+    png_writer.h
+)
+target_include_directories(generate_node_board PUBLIC ${PNG_INCLUDE_DIR})
+target_link_libraries(generate_node_board shared_settings ${PNG_LIBRARY})
+target_compile_features(generate_node_board PUBLIC cxx_std_17)
diff --git a/node_board/cmake/shared_settings.cmake b/node_board/cmake/shared_settings.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..19340ff1d74c3dd81346bb03ef2e0fa300b32068
--- /dev/null
+++ b/node_board/cmake/shared_settings.cmake
@@ -0,0 +1,35 @@
+# This file defines an interface library used to add common compile flags to all libraries and
+# executables in FunkyCT.
+
+add_library(shared_settings INTERFACE)
+
+# Warning flags
+target_compile_options(shared_settings INTERFACE
+    -Wall
+    -Wcast-align
+    -Wcast-qual
+    -Wextra
+    -Wundef
+    -Wuseless-cast
+    -Wzero-as-null-pointer-constant
+    -pedantic
+)
+
+# Speed flags
+target_compile_options(shared_settings INTERFACE -march=native -ffast-math)
+
+# Build type for profile generation
+target_compile_options(shared_settings INTERFACE $<$<CONFIG:ProfileGenerate>:
+    -fprofile-generate
+    -O3
+    -DNDEBUG
+>)
+target_link_options(shared_settings INTERFACE $<$<CONFIG:ProfileGenerate>:-fprofile-generate>)
+
+# Build type for profile use
+target_compile_options(shared_settings INTERFACE $<$<CONFIG:ProfileUse>:
+    -fprofile-use
+    -O3
+    -DNDEBUG
+>)
+target_link_options(shared_settings INTERFACE $<$<CONFIG:ProfileUse>:-fprofile-use>)
diff --git a/node_board/main.cpp b/node_board/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7d6a7409a56897d7f493d5f5bc2d0f183b13efb5
--- /dev/null
+++ b/node_board/main.cpp
@@ -0,0 +1,31 @@
+#include "png_writer.h"
+#include <iostream>
+
+//--------------------------------------------------------------------------------------------------
+int main() {
+    // All length measurements are in mm.
+    double width = 6;
+    double height = 14;
+
+    double ppmm = 50;
+    auto const to_px = [ppmm](double x) {
+        return static_cast<uint32_t>(ppmm * x);
+    };
+
+    uint32_t width_px = to_px(width);
+    uint32_t height_px = to_px(height);
+
+    PngWriter png_writer;
+    png_writer.allocate(width_px, height_px);
+    png_writer.set_all_pixels_black();
+
+    for (uint32_t y = 0; y < 10; ++y) {
+        for (uint32_t x = 0; x < 10; ++x) {
+            png_writer.set_pixel(x, y, 255);
+        }
+    }
+
+    png_writer.write("node_board_traces.png");
+
+    return 0;
+}
diff --git a/node_board/png_writer.cpp b/node_board/png_writer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c7da4af677a2caea77098a86d83402c144dd24eb
--- /dev/null
+++ b/node_board/png_writer.cpp
@@ -0,0 +1,119 @@
+#include "png_writer.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <cstring>
+
+#include <iostream>
+
+//..................................................................................................
+PngWriter::PngWriter(): width_(0), height_(0), row_pointers_(nullptr) {}
+
+//..................................................................................................
+PngWriter::~PngWriter() {
+    if (row_pointers_ != nullptr) {
+        free();
+    }
+}
+
+//..................................................................................................
+void PngWriter::free() {
+    for (int32_t y = 0; y < height_; y++) {
+        std::free(row_pointers_[y]);
+    }
+    std::free(row_pointers_);
+    width_ = 0;
+    height_ = 0;
+}
+
+//..................................................................................................
+void PngWriter::allocate(int32_t width, int32_t height) {
+    if (row_pointers_ != nullptr) {
+        free();
+    }
+    width_ = width;
+    height_ = height;
+    row_pointers_ = (png_bytep*)malloc(sizeof(png_bytep) * height_);
+    for (int y = 0; y < height_; y++) {
+        row_pointers_[y] = (png_bytep)malloc(row_size());
+    }
+}
+
+//..................................................................................................
+void PngWriter::set_pixel(int32_t x, int32_t y, uint8_t value) {
+    png_bytep row = row_pointers_[y];
+    png_bytep px = &(row[x * 3]);
+    px[0] = value;
+    px[1] = value;
+    px[2] = value;
+}
+
+//..................................................................................................
+void PngWriter::set_all_pixels_black() {
+    for (int y = 0; y < height_; y++) {
+        std::memset(row_pointers_[y], 0, row_size());
+    }
+    //for(int x = 0; x < width; x++) {
+    //    png_bytep px = &(row[x * 3]);
+    //    px[0] = 0;
+    //    px[1] = 0;
+    //    px[2] = 0;
+    //}
+}
+
+//..................................................................................................
+void PngWriter::write(char const* filename) {
+    auto fp = fopen(filename, "wb");
+    if (!fp) {
+        std::cout << "Couldn't make file\n";
+        abort();
+    }
+
+    png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+    if (!png) {
+        std::cout << "Couldn't make png_structp\n";
+        abort();
+    }
+
+    png_infop info = png_create_info_struct(png);
+    if (!info) {
+        std::cout << "Couldn't make png_structp\n";
+        abort();
+    }
+
+    if (setjmp(png_jmpbuf(png))) {
+        std::cout << "Couldn't set jump\n";
+        abort();
+    }
+
+    png_init_io(png, fp);
+    // Output is 8bit depth, RGB format.
+    png_set_IHDR(png,
+                 info,
+                 width_,
+                 height_,
+                 8,
+                 PNG_COLOR_TYPE_RGB,
+                 PNG_INTERLACE_NONE,
+                 PNG_COMPRESSION_TYPE_DEFAULT,
+                 PNG_FILTER_TYPE_DEFAULT
+    );
+    png_write_info(png, info);
+
+    if (png_get_rowbytes(png, info) != row_size()) {
+        std::cout << "Allocated bad amount of memory\n";
+        abort();
+    }
+
+    // To remove the alpha channel for PNG_COLOR_TYPE_RGB format,
+    // Use png_set_filler().
+    //png_set_filler(png_, 0, PNG_FILLER_AFTER);
+
+    png_write_image(png, row_pointers_);
+    png_write_end(png, NULL);
+
+    if (png && info) {
+        png_destroy_write_struct(&png, &info);
+    }
+
+    fclose(fp);
+}
diff --git a/node_board/png_writer.h b/node_board/png_writer.h
new file mode 100644
index 0000000000000000000000000000000000000000..7bedff046d37d6cfa27d003612b6bb9a6356cb36
--- /dev/null
+++ b/node_board/png_writer.h
@@ -0,0 +1,25 @@
+#include <png.h>
+#include <cstdint>
+
+//--------------------------------------------------------------------------------------------------
+// This class is derived from code by Guillaume Cottenceau, copyright 2002-2010 and distributed
+// under the X11 license. https://gist.github.com/niw/5963798
+class PngWriter {
+public:
+    PngWriter();
+    ~PngWriter();
+
+    void free();
+    void allocate(int32_t width, int32_t height);
+    png_bytep* row_pointers() { return row_pointers_; }
+    void set_pixel(int32_t x, int32_t y, uint8_t value = 255);
+    void set_all_pixels_black();
+    void write(char const* filename);
+
+private:
+    uint32_t row_size() { return 3 * width_; }
+
+    int32_t width_;
+    int32_t height_;
+    png_bytep* row_pointers_;
+};