Skip to content

🆕 Creating a new Project

In this example we create a project using the CMake build system.

In order to make a project you need 5 files:

  1. conanfile.txt: list of project dependencies.
  2. CMakeLists.txt: instructions describing the project's source files, executables and how to build them.
  3. main.cpp: application software
  4. libhal.tweaks.hpp: configuration file for libhal
  5. newlib.cpp : definitions of low level C functions

Quick Start

Clone this starter project:

git clone https://github.com/libhal/libhal-starter.git

Creating the conanfile.txt

A standard libhal conanfile.txt will look like this:

[requires]
libhal-lpc40/1.1.6

[tool_requires]
gnu-arm-embedded-toolchain/11.3.0
cmake-arm-embedded/0.1.1

[generators]
CMakeToolchain
CMakeDeps
  • [requires] lists the project dependencies.
    • Each libhal project needs a target library and this example uses libhal-lpc40 which is used for the SJ2 board or the LPC4078 micromod.
  • [tool_requires] lists the tools that are needed to build the project.
    • gnu-arm-embedded-toolchain/11.3.0 brings in the ARM GCC cross compiler which is used to compile to the code for ARM Cortex microcontroller.
    • cmake-arm-embedded/0.1.1 brings in cmake helper scripts and toolchain files for configuring cmake for the cross compiler.
  • [generators] list the generators which generate files for the project build systems.
    • CMakeToolchain: Generates toolchain cmake scripts based on the conan package recipe information.
    • CMakeDeps: Generates cmake package/configuration files for each library which can be

Making the CMakeLists.txt file

Below is the minimal amount of cmake code needed for a libhal project:

cmake_minimum_required(VERSION 3.20)

project(project_name.elf VERSION 0.0.1 LANGUAGES CXX)

find_package(libhal-lpc40 REQUIRED CONFIG)

add_executable(${PROJECT_NAME} main.cpp newlib.cpp)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20)
target_include_directories(${PROJECT_NAME} PUBLIC .)
target_link_libraries(${PROJECT_NAME} PRIVATE libhal::lpc4078)

arm_cortex_post_build(${PROJECT_NAME})

If you are unfamiliar with cmake, please take a look at the guide An Introduction to Modern CMake.

There are a few elements of the CMakeList.txt file which do not standard CMake:

  1. libhal::lpc4078: Defines the specific package component for the the lpc4078 micro-controller. Using this component will automatically use the minimum required compiler and link flags for the microcontroller as well as use the standard linker script for the device.
  2. arm_cortex_post_build(${PROJECT_NAME}): Provided by the toolchain file in the cmake-arm-embedded tool package. Generates the .hex (intel hex), .bin (binary), .S (disassembly) and .lst (disassembly with source interweaved).

Writing main.cpp

Read through the source code below to get an idea of whats needed:

#include <libhal-armcortex/dwt_counter.hpp>
#include <libhal-armcortex/startup.hpp>
#include <libhal-armcortex/system_control.hpp>
#include <libhal-lpc40/output_pin.hpp>
#include <libhal-lpc40/system_controller.hpp>
#include <libhal-util/steady_clock.hpp>

int
main()
{
  using namespace hal::literals;
  using namespace std::literals;

  // Initializing the data section initializes global and static variables and
  // is required for the standard C library to run.
  hal::cortex_m::initialize_data_section();
  hal::cortex_m::system_control::initialize_floating_point_unit();

  // Create a hardware counter
  auto& clock = hal::lpc40xx::clock::get();
  auto cpu_frequency = clock.get_frequency(hal::lpc40xx::peripheral::cpu);
  static hal::cortex_m::dwt_counter steady_clock(cpu_frequency);

  // Get an output pin to use as the LED pin control
  auto& led_pin = hal::lpc40xx::output_pin::get<1, 18>().value();

  while (true) {
    (void)led_pin.level(true);
    (void)hal::delay(steady_clock, 500ms);
    (void)led_pin.level(false);
    (void)hal::delay(steady_clock, 500ms);
  }

  return 0;
}

// When libhal.tweaks.hpp includes:
//
// #define BOOST_LEAF_EMBEDDED
// #define BOOST_LEAF_NO_THREADS
//
// Then Boost.LEAF needs this function to be defined
namespace boost {
void
throw_exception([[maybe_unused]] std::exception const& p_error)
{
  std::abort();
}
} // namespace boost

The newlib.cpp file

The newlib.cpp contains low level APIs used by the standard C library. With an OS these are implemented with OS APIs. For example, the function that provides memory to malloc() is the newlib API sbrk().

To learn more about how to write newlib.cpp see From Zero to main(): Bootstrapping libc with Newlib.

See libhal-starter/newlib.cpp for the default empty implementations.

Compiling the project

The command for building the project:

conan build .