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¶
- Visual Studio Code
- Microsoft Azure Sphere SDK
- GPIO_HighLevelAPP Sample
- mikroSDK 2.0 source code
- Driver source code of both Click boards
Info
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 MIKROBUS1_PWM AVNET_MT3620_SK_GPIO0
#define MIKROBUS2_CS AVNET_MT3620_SK_GPI035
#define MIKROBUS2_PWM AVNET_MT3620_SK_GPIO1
#define RE1_PIN MIKROBUS1_AN
#define RE2_PIN MIKROBUS1_RST
#define RE3_PIN MIKROBUS1_CS
#define RE4_PIN MIKROBUS1_PWM
#define RL1_PIN MIKROBUS2_CS
#define RL2_PIN MIKROBUS2_PWM
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);
else
GPIO_SetValue(relPinFd, GPIO_Value_Low);
if (ptr->relay2_status == 1)
GPIO_SetValue(re2PinFd, GPIO_Value_High);
else
GPIO_SetValue(re2PinFd, GPIO_Value_Low);
if (ptr->relay3_status == 1)
GPIO_SetValue(re3PinFd, GPIO_Value_High);
else
GPIO_SetValue(re3PinFd, GPIO_Value_Low);
if (ptr->relay4_status == 1)
GPIO_SetValue(re4PinFd, GPIO_Value_High);
else
GPIO_SetValue(re4PinFd, GPIO_Value_Low);
}
void relayState(relay_t* ptr)
{
if (ptr->relayl_status == 1)
GPIO_SetValue(r11PinFd, GPIO_Value_High);
else
GPIO_SetVa1ue(r11PinFd, GPIO_Value_Low);
if (ptr->relay2_status == 1)
GPIO_SetValue(r12PinFd, GPIO_Value_High);
else
GPIO_SetValue(r12PinFd, GPIO_Value_Low);
}
Take a look at the following code:
sigRelayPtr = open_relay(sigRelayState, sigRelayInit);
relayPtr = open_relay(relayState, relayInit);
sleep(1);
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");
sleep(1);
}
close_relay(sigRelayPtr);
close_relay(relayPtr);
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
Info
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_DIGITAL_INPUT = 0,
HAL_GPIO_DIGITAL_OUTPUT = 1
} 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);
else
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))
return RELAY_ERR_UNSUPPORTED_PIN_RELAY1;
if (DIGITAL_OUT_SUCCESS != digital_out_init(&ctx->rel2, cfg->rel2))
return RELAY_ERR_UNSUPPORTED_PIN_RELAY2;
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;
relay_cfg_setup(&relayl_cfg);
relay_cfg_setup(&relay2_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.
#define MIKROBUS1_CS AVNET_MT3620_SK_GPIO34
#define MIKROBUS1_PWM AVNET_MT3620_SK_GPIO0
#define MIKROBUS2_CS AVNET_MT3620_SK_GPIO35
#define MIKROBUS2_PWM AVNET_MT3620_SK_GPIO1
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).
Helpful links¶
On the links below you can see different ways of porting Click board examples: