Embedded development project structure with Git X-Modules
Tags: git, embedded, xmodules
Published: 2020-12-21 11:18:10

---
canonical_url: https://support.tmatesoft.com/t/embedded-development-project-structure-with-git-x-modules/2527
---

Embedded development, just like any other, often depends on shared code components such as libraries for specific hardware. However, there's no de-facto industry standard for managing modular projects in this area.  We are going to describe one of the ways to do it on a Git repository level.   We will mostly concentrate on project organization topics rather than on particular questions of embedded development.

## Infrastructure ##
As an example,  we will create a simple program that blinks the LEDs of [STM32F4DISCOVERY](https://www.st.com/en/evaluation-tools/stm32f4discovery.html) board by STMicroelectronics.  I assume we have a STM32F4DISCOVERY board with [ST32F407VGT6](https://www.st.com/en/microcontrollers-microprocessors/stm32f407-417.html) MCU. I'm using Debian 10.6, but for any other OS, the steps are similar. I have 'stlink-tools' and 'gcc-arm-none-eabi' packages installed. The former is a collection of tools (and 'st-flash' in particular) to work with the Discovery board (e.g. 'st-flash' writes the compiled binary file to the MCU's flash using STLink protocol). The latter is a generic ARM cross-compiler as ST32F407VGT6 is ARM Cortex M4 based MCU.
 
 If you don't have these packages installed do that now:
 ```
 sudo apt install stlink-tools gcc-arm-none-eabi
 ```
 
 STMicroelectronics provides a standard library for ST32F407VGT6 ([STSW-STM32065](https://www.st.com/content/st_com/en/products/embedded-software/mcu-mpu-embedded-software/stm32-embedded-software/stm32-standard-peripheral-libraries/stsw-stm32065.html)). But instead of using it (not everybody is happy with its APIs), we will develop our own library by using MCU datasheets directly. Another reason to develop our own library is to demonstrate a typical scenario when libraries are developed together with the main project itself and can be reused in other projects.
 
 Although we will have only one project in this post, we will assume there're other projects reusing the same library. In particular, fixes and updates to the library should get into these projects as well.
 
 We will assume the code to be stored in Git and Atlassian [Bitbucket Server](https://www.atlassian.com/software/bitbucket/download)/[Data Center](https://www.atlassian.com/enterprise/data-center/bitbucket) is used on the server's side. This is not necessary but would give us a nice UI.
 
 ## Project Structure ##
Our project will consist of the following major components:
 * main project source file;
 * generic library supporting ST32F407VGT6 MCU;
 * files and compiler options needed to cross-compile the C code to the ARM platform.


To cross-compile a project, one has to use a number of specific compiler options and also a startup and a linker script file. When having several projects for the same MCU, it makes sense to share these files and options across projects. Thus we will have a common component dedicated to these compilation-related files. We will name this component **'common'**.
 
 We will name the library **'stm32'** component. The main project will be named **'embedded-example'**.
![01-structure|690x269](https://support.tmatesoft.com/uploads/default/original/1X/0e1e75346a40b9f94e57ec1c670fd58ddccddb25.png) 


## Step 1. Create the 'stm32' project ##
Create 'stm32' Git repositrory with Atlassian Bitbucket Server/Data Center UI 
![02-create-stm32-repository|653x500](https://support.tmatesoft.com/uploads/default/original/1X/873247724cc29028714b3a4528c32ef6c72b1b8b.png) 
and clone it.

```
git clone http://example.org/scm/ee/stm32.git stm32/
cd stm32/
```

Create CMakeLists.txt file there:
```
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
project(stm32)

add_library(stm32 pin.c gpio.c util.c)
target_include_directories(stm32 PUBLIC .)
```
This CMakeLists.txt file describes a project with three C files and tells all the projects that would include it that the include directory is at the project root. So the files structure will be:

```
├── CMakeLists.txt
├── gpio.c
├── gpio.h
├── pin.c
├── pin.h
├── util.c
└── util.h
```

Now create these files and their headers: 

* [util.h](https://support.tmatesoft.com/uploads/short-url/yECbnDvc85xTjha0nzKwYJj2bT8.h) & [util.c](https://support.tmatesoft.com/uploads/short-url/iySFXJyPbs5GgowkwUCfysOnJu7.c) - they contain convenience functions to set and get special bits;
* [gpio.h](https://support.tmatesoft.com/uploads/short-url/iDFjZCjuxX6sz1c5qP6oGKUe7X0.h) & [gpio.c](https://support.tmatesoft.com/uploads/short-url/wJaspm7z1Pl1q178PeUUXKSebfM.c) - they contain a dull and straightforward implementation of ST32F407VGT6 datasheet GPIO specifications;
* [pin.h](https://support.tmatesoft.com/uploads/short-url/t3MMaCZjA7qrduIpmzeCcwyGYBs.h) & [pin.c](https://support.tmatesoft.com/uploads/short-url/wV6KyqgSBTju41yXc2ggYCF9u0p.c) - they define pin names like `hw_pin_PD12` and their convenience equivalents like `hw_pin_led_green`. 

For instance, on STM32F4DISCOVERY PD12 pin of the MCU is connected to the green LED, that's why it's convenient to define:
```
#define hw_pin_led_green hw_pin_PD12
```

Now add, commit, and push the changes to 'stm32' library:
```
git add *.c
git add *.h
git add CMakeLists.txt
git commit -m "Initial."
git push origin master
```
![03-stm32-repository-toc|436x499](https://support.tmatesoft.com/uploads/default/original/1X/e1dc77244270992b30b3b750ff9c9907265f4c95.png) 
I would note that as this library is self-contained, one can even compile (but not cross-compile, so far) it:
```
mkdir build
cd build
cmake ..
make
```
If everything compiles, it's a good indicator, though a pretty useless property as our target platform is ARM.

## Step 2. Create the 'common' project ##
This project will contain the common configuration that is shared between projects. It's also convenient to put it into a separate Git repository and insert it into each project repository to be included by the project CMakeLists.txt.

Create  the 'common' repository with Atlassian Bitbucket Server/Data Center 

![04-create-common-repository|659x500](https://support.tmatesoft.com/uploads/default/original/1X/15877b568888b77d3eb7b948b045d42fe461e0a9.png) 
and clone the newly created repository:

```
git clone http://example.org/scm/ee/common.git common/
cd common/
```
Now create `vars.cmake`:

```
set(CMAKE_SYSTEM_NAME      Generic)
set(CMAKE_SYSTEM_VERSION   1)
set(CMAKE_SYSTEM_PROCESSOR arm-eabi)

set(CMAKE_C_COMPILER       arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER     arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER     arm-none-eabi-as)
set(CMAKE_OBJCOPY          arm-none-eabi-objcopy)
set(CMAKE_OBJDUMP          arm-none-eabi-objdump)

set(CMAKE_C_FLAGS "-mthumb -mcpu=cortex-m4 -fno-builtin -Wall -Wno-pointer-to-int-cast -std=gnu99 -fdata-sections -ffunction-sections" CACHE INTERNAL "c compiler flags")
set(CMAKE_CXX_FLAGS "-mthumb -mcpu=cortex-m4 -fno-builtin -Wall -Wno-pointer-to-int-cast -fdata-sections -ffunction-sections" CACHE INTERNAL "cxx compiler flags")
set(CMAKE_ASM_FLAGS "-mthumb -mcpu=cortex-m4" CACHE INTERNAL "asm compiler flags")

set(CMAKE_EXE_LINKER_FLAGS "-nostartfiles -Wl,--gc-sections -mthumb -mcpu=cortex-m4" CACHE INTERNAL "exe link flags")
set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS CACHE INTERNAL "reset default flags")

set(STM32_STLINK_CLI_EXECUTABLE "st-flash")
```
This file defines various variables needed for the cross-compilation of sources with ARM as the target platform. It sets compiler flags and a path to the 'st-flash' utility that writes binary files to MCU.

Also add `stm32f407vg_flash.ld`, `startup_stm32f40xx.s`, and `stm32f4xx.h` files. I will not cite them here, but they are quite standard and distributed by STMicroelectronics.

So the repository content becomes:
```
├── startup_stm32f40xx.s
├── stm32f407vg_flash.ld
├── stm32f4xx.h
└── vars.cmake
```
Commit and push the changes:

```
git add vars.cmake stm32f4xx.h startup_stm32f40xx.s stm32f407vg_flash.ld
git commit -m "Initial."
git push origin master
```
![05-common-repository-toc|576x500](https://support.tmatesoft.com/uploads/default/original/1X/b11be8ad976df1d692a6da47b61acea7545c29e1.png) 

## Step 3. The project repository ##
Now let's create the main project repository. The repository will have the following structure:
```
├── CMakeLists.txt
├── common   <--- here common.git will be inserted
├── libs
│   └── stm32  <--- here stm32.git will be inserted
│
└── src
    └── main.c
```
So create an empty Git repository using Atlassian Bitbucket Server/Data Center UI

![06-create-embedded-example-repository|660x500](https://support.tmatesoft.com/uploads/default/original/1X/d976265ffdf0b669d888fb3b40eb5424eae003bc.png) 
and clone it:
```
git clone http://example.org/scm/ee/embedded-example.git embedded-example/
cd embedded-example/
```

Create `CMakeLists.txt` there:
```
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
include(common/vars.cmake)
project(embedded-example)
enable_language(C ASM)

add_subdirectory(libs/stm32)

set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -T${CMAKE_SOURCE_DIR}/common/stm32f407vg_flash.ld")

add_executable(${PROJECT_NAME}.elf
    ${CMAKE_SOURCE_DIR}/common/startup_stm32f40xx.s
    ${CMAKE_SOURCE_DIR}/src/main.c)
target_include_directories(${PROJECT_NAME}.elf PRIVATE stm32)
target_link_libraries(${PROJECT_NAME}.elf PRIVATE stm32)

add_custom_target(${PROJECT_NAME}.hex DEPENDS ${PROJECT_NAME}.elf COMMAND ${CMAKE_OBJCOPY} -Oihex ${PROJECT_NAME}.elf ${PROJECT_NAME}.hex)
add_custom_target(${PROJECT_NAME}.bin DEPENDS ${PROJECT_NAME}.elf COMMAND ${CMAKE_OBJCOPY} -Obinary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin)
set(STLINK_CMD ${STM32_STLINK_CLI_EXECUTABLE} write ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.bin 0x8000000)
add_custom_target(write-flash DEPENDS ${PROJECT_NAME}.bin COMMAND ${STLINK_CMD})
```

It's important to call `include()` on the file with common variables before `project()` call otherwise CMake falls into an infinite loop (I would consider this strange behavior as a CMake bug). It's also important to specify the linker script that we've put into the 'common.git' project.

`add_subdirectory()` call includes the library. We can include several libraries there. The libraries will be inserted in the `libs/` directory.

The last line creates a `make write-flash` target that will not only create a binary file for the MCU but also will write it into its flash using STLink protocol. `${STM32_STLINK_CLI_EXECUTABLE}` is defined in the 'common' project.

Now create `src/main.c` file:
```
#include <gpio.h>

hw_pin_t leds[] = {
  hw_pin_led_red,
  hw_pin_led_green,
  hw_pin_led_blue,
//  hw_pin_led_orange
};

int leds_count = sizeof(leds) / sizeof(hw_pin_t);

void SystemInit() {
}

int main() {
  hw_gpio_configure(hw_pin_led_blue, hw_gpio_mode_output_pull_push, hw_gpio_speed_2mhz, hw_gpio_alternate_function_none);
  hw_gpio_configure(hw_pin_led_green, hw_gpio_mode_output_pull_push, hw_gpio_speed_2mhz, hw_gpio_alternate_function_none);
  hw_gpio_configure(hw_pin_led_orange, hw_gpio_mode_output_pull_push, hw_gpio_speed_2mhz, hw_gpio_alternate_function_none);
  hw_gpio_configure(hw_pin_led_red, hw_gpio_mode_output_pull_push, hw_gpio_speed_2mhz, hw_gpio_alternate_function_none);
  
  int current_led = 0;

  while (TRUE) {  
    hw_gpio_set(leds[current_led], FALSE);
    current_led = (current_led + 1) % leds_count;
    hw_gpio_set(leds[current_led], TRUE);
    
    int delay = 1000000;
    while (delay--) {
    }
  }
}
```

This file is simple: it initializes pins related to LEDs and blinks with red, green, and blue pins. We've commented out the orange pin as default STM32F4DISCOVERY firmware blinks with all 4 LEDs and we want to differ from that.

Commit and push the changes:
```
git add src/main.c
git add CMakeLists.txt
git commit -m "Initial."
git push origin master
```
![07-embedded-example-repository-toc|682x500](https://support.tmatesoft.com/uploads/default/original/1X/d9c915116b11961601265883adb33a94552798b9.png) 

## Step 4. Insert 'common.git' and 'stm32.git' repositories into 'embedded-example.git' ##
To insert one repository to another we will use Git [X-Modules](http://tmatesoft.com/x-modules/). For Atlassian Bitbucket Server/Data Center there's a dedicated app with a nice UI. For other Git servers use the: Git X-Modules Command-Line Tool.

Make sure you have X-Modules app installed into Atlassian Bitbucket Server/Data Center. If not, visit `Administration | Find new apps | Search the [Marketplace](https://marketplace.atlassian.com/apps/1223696/git-x-modules-for-bitbucket-server)` and type "X-Modules" from Bitbucket Server/Data Center UI.
![07-install-x-modules-app|690x203](https://support.tmatesoft.com/uploads/default/original/1X/7c874cca26baf6d433d63b89b39b0eedf20c36d1.png) 
Go to  the 'embedded-example' Git repository page. When the Git X-Modules app is installed there's  a Git X-Modules button on the left panel, click it.
![09-x-modules-button|690x181](https://support.tmatesoft.com/uploads/default/original/1X/30c200fa3dd3234e27f1782f55b570513b4dbce7.png) 
Then click 'Add Module' to add the first module (let it be 'common.git').
![11-x-modules-add-first-module|690x275](https://support.tmatesoft.com/uploads/default/original/1X/46e007817169e53196cb4ad23826dfded5c33371.png) 
Choose 'common' repository and 'master' branch. Make sure "This Repository Path:" is 'common'. It's the path where the repository will be inserted:
![11-x-modules-add-common-preview|690x443](https://support.tmatesoft.com/uploads/default/original/1X/7dd992cca949dbd4265d54a9ec7e16612197263f.png) 
Click 'Add Module'. Without applying the changes click 'Add Module' again to add 'stm32' repository as module.
![12-x-modules-add-second-module|690x422](https://support.tmatesoft.com/uploads/default/optimized/1X/c3d9f36974d6ee31ce79b5c41e851ea4b2636ab3_2_1035x633.png) 
Choose 'stm32' repository and 'master' branch.
![13-x-modules-choose-stm32-repository|690x464](https://support.tmatesoft.com/uploads/default/original/1X/0e80fd157ab784d46ac28125e52195f58ac7978e.png) 
Make sure "This Repository Path:" is "libs/stm32", this is the insertion path for 'stm32.git' repository.
![14-x-modules-choose-stm32-module-path|690x453](https://support.tmatesoft.com/uploads/default/original/1X/78ae9da034caa573884d7a570a3c564c6579a09b.png) 
Click 'Add Module' and apply the changes.
![15-x-modules-apply-changes|664x500](https://support.tmatesoft.com/uploads/default/original/1X/fd6e837b51aee41a2c17c4f048075f2a94bfe91b.png) 
Now the repositories are inserted as X-Modules.
![16-embedded-example-repository-toc|560x500](https://support.tmatesoft.com/uploads/default/original/1X/1b622d36989c04a8e3ad8a1fa541c29660cb37ee.png) 
This means that 'common.git' and 'stm32.git' are synchronized with corresponding directories ('common' and 'libs/stm32' respectively) of 'embedded-example.git'.

Fetch the changes from 'embedded-example':

```
cd embedded-example/
git pull --rebase
```

Now the project contains everything:

```
├── CMakeLists.txt
├── common
│   ├── startup_stm32f40xx.s
│   ├── stm32f407vg_flash.ld
│   ├── stm32f4xx.h
│   └── vars.cmake
├── libs
│   └── stm32
│       ├── CMakeLists.txt
│       ├── gpio.c
│       ├── gpio.h
│       ├── pin.c
│       ├── pin.h
│       ├── util.c
│       └── util.h
└── src
    └── main.c
```

Check that the project compiles:
```
mkdir build
cd build
cmake ..
make
make write-flash
```
![](https://support.tmatesoft.com/uploads/default/original/1X/5cfee0d23ea8aa56fd5058d3f1a325d5284c198e.gif)
At the moment of running `make write-flash`, the STM32F4DISCOVERY board should be connected. If you do everything correctly, you'll see 3 LEDs blinking.

This repository structure can be reused for any other ST32F407VGT6-based project, it's enough to insert 'common' and 'stm32' at the corresponding places. Moreover, the directories are bi-directionally synchronized with the inserted repositories, so one can change, see the results, and modify 'stm32' directly from the project repository.