Skip to content

mikroSDK 2.0 Porting Guide

The purpose of this document is to demonstrate and provide guidance on how to port parts of mikroSDK to be used with other SDKs and toolchains. This will be very useful in case you wish to use Click board examples in your SDK, or simply wish to use mikroSDK drivers in your project in general.

In order to make it easier to understand, we will go over a practical example, which shows how to port the GPIO driver from mikroSDK to Microsoft Azure Sphere SDK.
The main idea is to add the mikroSDK GPIO driver to the Azure Sphere project and then implement the Azure Sphere SDK high level GPIO driver into mikroSDK HAL (hardware abstraction layer). This way the GPIO high level driver of the Azure Sphere SDK takes care of hardware abstraction.

Once a mikroSDK driver, for example GPIO, is properly ported and a new modified HAL is created, it enables you to use hundreds of other Click boards that use GPIO to communicate.
Same applies to the SPI driver, I2C, UART, etc...

A proper porting of a mikroSDK driver results in creation of a new HAL, none or very minimal changes to the Click board driver and minimal changes to the Click board example.

This guide, when broken down, reveals the following steps:

1) Setting up PC and installing necessary software

2) Comparing and understanding the differences between mikroSDK and Azure Sphere SDK

3) Porting the mikroSDK GPIO driver to Azure Sphere SDK

4) Programming and testing the project

Hardware used in this guide

Software used in this guide


You do not necessarily have to have these Click boards. They were used only as an example in this guide. It is possible to use any Click board based on the GPIO module in order to follow this guide.

Setting up PC and installing necessary software

Software setup

First thing you need to do before you start porting mikroSDK 2.0 is to install the software required for this project. Follow the steps from the guide on this link to find out how to configure your PC and your project, including Visual Studio Code setup, CLI setup, SDK setup, board configuration using CLI and programming process.
Make sure you choose the correct SDK which contains setup for Visual Studio Code (required for this guide), as there is another version which contains setup for Visual Studio IDE.
After following these steps succesfully, you will be ready to start the mikroSDK porting procedure.

Comparing and understanding the differences between mikroSDK and Azure Sphere SDK

The first thing that you should do when trying to port any of the mikroSDK drivers to your project is to compare your application/project/drivers with the mikroSDK drivers, the differences and similarities in organization, structure, implementation.
It could be useful if you successfully modify your project to run the target Click board on your system. This will allow you to see how the Click board exactly works and what modifications need to be made in order for it to run on your system.

In this example we use Azure Sphere Starter Kit along with the Relay and Signal Relay Click boards.
Let us examine how to run both of these Click boards on the Azure Sphere Starter Kit by modifying the GPIO_HighLevelApp project. Signal Relay Click will be used on mikroBUS1, while Relay Click will be used on mikroBUS2 slot.
In that project, we will define and initialize GPIO pins which are routed to the two mikroBUS slots:

#define MIKROBUS1_AN   AVNET_MT3620_5K_GPI042   //click#1=GPI00; click#2=GPIO1
#define MIKROBUS1_RST AVNET_MT3620_SK_GPI016    //click#1=GPI034; click#2=GPI035
#define MIKROBUS1_CS   AVNET_MT3620_SK_GPI034
#define MIKROBUS2_CS  AVNET_MT3620_SK_GPI035


static int relPinFd; //relay #1, mikrobus #1
//static GPIO_Value_Type relaylPin;
static int re2PinFd; //relay #2, mikrobus #1
static int re3PinFd; //relay #3, mikrobus #11
static int re4PinFd; //relay #4, mikrobus #1
//static GPIO_Value_Type relay2Pin;
//static int fd;
static int r11PinFd; //relay #1, mikrobus #2
static int r12PinFd; //relay #2, mikrobus #2
void sigRelayInit(void)
    relPinFd = GPIO_OpenAsOutput(RE1 _PIN, GPIO_OutputMode_PushPull, GPIO_Value_Low);
    re2PinFd = GPIO_OpenAsOutput(RE2 _PIN, GPIO_OutputMode_PushPull, GPIO_Value_Low);
    re3PinFd = GPIO_OpenAsOutput(RE3 _PIN, GPIO_OutputMode_PushPull, GPIO_Value_Low);
    re4PinFd = GPIO_OpenAsOutput(RE4 _PIN, PIN GPIO_OutputMode_PushPull, GPIO_Value_Low);

void relayInit(void)
    r11PinFd = GPIO_OpenAsOutput(RL1_PIN, GPIO_OutputMode_PushPull, GPIO_Value_Low);
    rl2PinFd = GPIO_OpenAsOutput(RL2_PIN, GPIO_OutputMode_PushPull, GPIO_Value_Low);

So, if you wish to control both Click boards at the same time you need to have the exact number of static global variables (pin objects) determined by the number of used GPIO pins. These global variables will store the ID of the initialized pin and will be used to address the desired pin. This is how the Azure Sphere SDK GPIO driver functions.
In this example we wrote the following code to control the state of the each of the used pins:

void sigRelayState(relay_t* ptr)
    if (ptr->relayl_status == 1)
        GPIO_SetValue(relPinFd, GPIO_Value_High);
        GPIO_SetValue(relPinFd, GPIO_Value_Low);

    if (ptr->relay2_status == 1)
        GPIO_SetValue(re2PinFd, GPIO_Value_High);
        GPIO_SetValue(re2PinFd, GPIO_Value_Low);

    if (ptr->relay3_status == 1)
        GPIO_SetValue(re3PinFd, GPIO_Value_High);
        GPIO_SetValue(re3PinFd, GPIO_Value_Low);

    if (ptr->relay4_status == 1)
        GPIO_SetValue(re4PinFd, GPIO_Value_High);
        GPIO_SetValue(re4PinFd, GPIO_Value_Low);

void relayState(relay_t* ptr)
    if (ptr->relayl_status == 1)
        GPIO_SetValue(r11PinFd, GPIO_Value_High);
        GPIO_SetVa1ue(r11PinFd, GPIO_Value_Low);

    if (ptr->relay2_status == 1)
        GPIO_SetValue(r12PinFd, GPIO_Value_High);
        GPIO_SetValue(r12PinFd, GPIO_Value_Low);

Take a look at the following code:

sigRelayPtr = open_relay(sigRelayState, sigRelayInit);
relayPtr = open_relay(relayState, relayInit);
i = 0;

while (i++ < runtime)
    relaystate(sigRelayPtr, (i - &1) ? relayl_set : relayl_c1r);
    relaystate(sigRelayPtr, (i - &2) ? relay2_set : relay2_clr);
    relaystate(sigRelayPtr, (i - &1) ? relay3_set : relay3_clr);
    relaystate(sigRelayPtr, (i - &2) ? relay4_set : relay4_clr);
    relaystate(relayPtr, (i & 2) ? relayl_set : relay1_c1r);
    relaystate(relayPtr, (i & 1) ? relay2_set : relay2_clr);

    Log_Debug("(%d) RE1-%s, RE2 %s, RE3 %s, RE4 %s\n", i,
              relaystate(sigRelayPtr, relayl_rd) ? "ON" : "OFF",
              relaystate(sigRelayPtr, relay2_rd) ? "ON" : "OFF",
              relaystate(sigRelayPtr, relay3_rd) ? "ON" : "OFF",
              relaystate(sigRelayPtr, relay4_rd) ? "ON" : "OFF");
    Log_Debug(" RL1 %s, RL2 %s\n",
              relaystate(relayPtr, relayl_rd) ? "ON " : "OFF",
              relaystate(relayPtr, relay2_rd) ? "ON " : "OFF");


This is how we actually control the state of the relay output.
Now, if we compare this modified example with the default GPIO example (from the GPIO_HighLevelApp), we can notice that the two are very similar and that we didn't have to make a lot of modifications, but they are essential to this code.

Now we can start porting the mikroSDK GPIO driver to the GPIO_HighLevelApp project/Microsoft Azure Sphere SDK.

mikroSDK porting procedure

To begin, let us copy all necessary source and header files from the mikroSDK GPIO driver into the project based on the GPIO_HighLevelApp example.

Required files:

Driver Layer:

  • drv_digital_out.c
  • drv_digital_out.h
  • drv_name.h

Hardware Abstraction Layer (HAL):

  • hal_gpio.c
  • hal_gpio.h
  • hal_target.h

HAL Low Level Layer (HAL LL):

  • hal_ll_target_names.h
  • hal_ll_gpio.c
  • hal_ll_gpio.h
  • hal_ll_target.h


Files in italics are only necessary if you want to implement your SDK into HAL Low Level Layer of the mikroSDK. In this guide, we will put our implementation directly in the HAL layer, so we do not need these files.

We also need the library source code of the Relay Click. It can be downloaded from Libstock, just like other Click libraries.

Relay Click library consists of two files:

  • relay.c
  • relay.h

In the next step we should take a look at the mikroSDK GPIO driver, HAL (Hardware Abstraction Layer) and HAL Low Level code. If we check the implementation of these layers and compare them with implementation of the GPIO_HighLevelApp example, which includes the GPIO module from the Microsoft Azure Sphere SDK/../../../../applibs/gpio.h, we can conclude that function implementation and pin object structure are similar, but have some differences. Now we have to find a way to include the functions from the applibs/gpio.h library into the mikroSDK GPIO driver layer without making any modifications to this layer. This is imperative in case we want to maintain compatibility with other architectures.

Let us start from the pin object structure. By comparing implementation of the GPIO_HighLevelApp example with the mikroSDK GPIO driver and HAL layer, we can see that we can use fields from the pin object structure hal_gpio_pin_t, specifically the mask field, to store the result of executing the GPIO_Open() function.

 *  Handle and mask types.
typedef handle_t hal_gpio_base_t;
typedef hal_ll_gpio_mask_t hal_gpio_mask_t;

* Enum used for pin direction selection.
typedef enum
} hal_gpio_direction_t;

 *  Enum-used-for pin direction selection.
typedef struct hal_gpio_t
    hal_gpio_base_t base;
    hal_gpio_mask_t mask;

* Pin and port data types.
typedef struct hal_gpio_t hal_gpio_pin_t;
typedef struct hal_gpio_t hal_gpio_port_t;

We can make use of the hal_gpio_direction_t direction parameter to determine whether to call the GPIO_OpenAsInput() or GPIO_OpenAsOutput(), depending on whether the pin is to be configured as input or output respectively. We will use the hal_pin_name_t name parameter for identifying the selected pin.
Adhering to these guidelines, we can alter the hal_gpio_configure_pin() function, like so:

void hal_gpio_configure_pin(halgpio_pin_t *pin, hal_pin_name t name, hal_gpio_direction_t direction)
    if (direction == GPIO_DIGITAL_INPUT)
        pin->mask = GPIO_OpenAslnput(name);
        pin->mask = GPIO_OpenAsOutput(name, GPIO_OutputMode_PushPull, GPIO_Value_Low);

This parameter will determine which pin that we want to use, and must be configured by using relay_cfg_t configuration object. If we want to use two same Relay Click boards, one in each mikroBUS socket, just like we are doing in this example, we need to have two relay_t context objects, one for each Relay Click board, as well as two relay_cfg_t configuration objects.

relay_err_t relay_init(relay_t *ctx, relay_cfg_t *cfg)
    if (DIGITAL_OUT_SUCCESS != digital_out_init(&ctx->rel1, cfg->rel1))
    if (DIGITAL_OUT_SUCCESS != digital_out_init(&ctx->rel2, cfg->rel2))

    return RELAY_ERR_OK;
static relay_t relayl;
static relay_t relay2;

void application_init(void)
    relay_cfg_t relayl_cfg;
    relay_cfg_t relayl_cfg;


    relayl_cfg.rell = MIKROBUSl_PWM;
    relayl_cfg.rel2 = MIKROBUSl_CS;
    relay2_cfg.rell = MIKROBUS2_PWM;
    relay2_cfg.rel2 = MIKROBUS2_CS;

    relay_init(&relayl, &relayl_cfg);
    relay_init(&relay2, &relayl_cfg);

Now that we have adjusted the pin object structure from mikroSDK, it’s very easy to modify all other functions that are used in the GPIO module of the mikroSDK project. All that is left to change now is for GPIO HAL to call functions from the GPIO library of the Microsoft Azure Sphere SDK (applibs/gpio.h). The pin object structure, as the main argument of all of these functions will provide everything they require.

The similar code which allows to control the selected GPIO can be found in the mikroSDK project in the GPIO HAL Low Layer (hal_ll_gpio.c), it can be used in our implementation with necessary modifications. Also, in this case it’s completely correct if we put the low level implementation directly in the GPIO HAL (hal_gpio.c).

Here is how we modified the functions from the mikroSDK GPIO HAL layer:

hal_gpio_pin_data_t hal_gpio_read_pin_input(hal_gpio_pin_t *pin)
    GPIO_Value_Type idr_value;

    GPIO_GetValue(pin->mask, &idr_value);

    return (hal_gpio_pin_data_t)idr_value;
void hal_gpio_write_pin_output(hal_gpio_pin_t *pin, hal_gpio_pin_data_t value)
    GPIO_SetValue(pin->mask, value);
void hal_gpio_toggle_pin_output(hal_gpio_pin_t *pin)
    hal_gpio_pin_data_t value = hal_gpio_read_pin_output(pin);
    hal_gpio_write_pin_output(pin, !value);
void hal_gpio_set_pin_output(hal_gpio_pin_t *pin)
    GPIO_SetValue(pin->mask, GPIO_Value_High);

By doing this, we have completed the mikroSDK porting procedure for the GPIO module. Now, there is a new low level implementation of the GPIO HAL which uses functions from the Microsoft Azure Sphere SDK GPIO layer.
mikroSDK GPIO driver, together with all other drivers, has a unique interface which allows controlling of the GPIO module by calling the functions from the GPIO HAL. This results in having unique functions to control the GPIO module for any supported architecture. The same applies to other mikroSDK modules.

Now, if we take a look at the Relay Click or Signal Relay Click driver, we can see that both of them use functions from the mikroSDK GPIO driver layer in their implementation. That means that we are now able to include this exact Click driver in our code to perform the control of the Relay/Signal Relay Click on the Azure Sphere Starter Kit, or any other supported board -- a same Click driver with unique interface for all supported boards and architectures.

If we go back to the GPIO_HighLevelApp project, which we modified to be able to control the Relay and Signal Relay Click boards, now there is a new, much better solution. Because of the changes we've made, mikroSDK project is now a part of the Microsoft Azure Sphere SDK project and that allows us to use the Relay driver directly in our GPIO_HighLevelApp project. This also means that, if we wish to use any other Click driver based on the GPIO module, we just need to download it from Libstock and include it in our project.

The final step would be to alter our example, so that it calls functions from the Relay driver, just like in the official mikroSDK project.
In the example below, we placed two Relay Click boards in each mikroBUS socket, both controlled by the same Relay driver:

* copyright (c) 2018, James Flynn
* SPDX-License-Identifier: MIT

#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>

// applibs_versions.h defines the API struct versions to use for applibs APIs.
#include "applibs_versions.h"
#include <applibs/log.h>

#include <hw/avnet_mt3620_sk.h>
#include "relay.h"

// pin map of each mikroBUS socket for the RELAY click board.

static relay_t relay1;
static relay_t relay2;

void application_init( void )
    relay_cfg_t relay1_cfg;
    relay_cfg_t relay2_cfg;

    relay_cfg_setup( &relay1_cfg );
    relay_cfg_setup( &relay2_cfg );

    relay1_cfg.rel1 = MIKROBUS1_PWM;
    relay1_cfg.rel2 = MIKROBUS1_CS;
    relay2_cfg.rel1 = MIKROBUS2_PWM;
    relay2_cfg.rel2 = MIKROBUS2_CS;

    relay_init( &relay1, &relay1_cfg );
    relay_init( &relay2, &relay2_cfg );

    relay_default_cfg( &relay1 );
    relay_default_cfg( &relay2 );

    Log_Debug( "\n\n" );
    Log_Debug( "     ****\r\n" );
    Log_Debug( "    **  **     SW reuse using C example\r\n" );
    Log_Debug( "   **    **    for the Relay Clicks\r\n" );
    Log_Debug( "  ** ==== **\r\n" );
    Log_Debug( "\r\n" );
    Log_Debug( "This demo simply alternates the relays through different\n" );
    Log_Debug( "states at a 1 second interval. This demo requires using Socket #1 and Socket #2.\r\n" );
    sleep( 1 );

void application_task( void )
    static relay_state_t relay1_state = RELAY_STATE_OFF;
    static relay_state_t relay2_state = RELAY_STATE_OFF;

    relay_set_state( &relay1, RELAY_SEL_1, relay1_state );
    relay_set_state( &relay1, RELAY_SEL_2, !relay1_state );
    relay_set_state( &relay2, RELAY_SEL_1, !relay2_state );
    relay_set_state( &relay2, RELAY_SEL_2, relay2_state );

    Log_Debug( "MIKROBUS1: RL1 %s, RL2 %s",
        relay1_state ? "ON " : "OFF",
        !relay1_state ? "ON " : "OFF" );
    Log_Debug( "MIKROBUS2: RL1 %s, RL2 %s\n",
        !relay2_state ? "ON " : "OFF",
        relay2_state ? "ON " : "OFF" );

    relay1_state = !relay1_state;
    relay2_state = !relay2_state;
    sleep( 1 );

void main( void )
    application_init( );

    while ( 1 )
        application_task( );

    exit( EXIT_SUCCESS );

Programming and testing

In case you haven't followed all the steps from this guide about what is necessary to do if you want to program your device and test your project, now would be the time to do so and to make sure you succesfully completed the following steps:

  • creating a Microsoft account (if you do not have one already)
  • claiming your device
  • configuring networking for development
  • building a high-level application

This will allow you to start debugging in the Visual Studio Code [ F5 ], or to run your project without debugging [ Ctrl+F5 ]. If everything was successful, meaning there were no errors in the output terminal, you should see how your device/board performs the controlling of these two Relay Click boards.
This is an enough to verify that the mikroSDK porting procedure was successful.

You can repeat the same procedure to port any other driver from the mikroSDK project by using some other Click board, which utilizes that module (ADC, I2C, SPI, et cetera).

On the links below you can see different ways of porting Click board examples: