Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT

Developing Applications for Azure Sphere, Part 2: Building High-Level Applications

5.00/5 (4 votes)
31 Oct 2021CPOL14 min read 6.2K   26  
This article explains how to build, program, and deploy high-level applications that run on the Azure Sphere OS.
Azure Sphere supports two different types of applications. High-level applications run on the Cortex-A7 and perform general-purpose operations. Real-time applications execute on the real-time cores (Cortex-M4 microcontroller cores). This article explains how to build and code high-level applications.

Introduction

As discussed in the preceding article, the only device certified to run the Azure Sphere OS is the MT3620. This contains three processing cores: a general-purpose Cortex-A7 core and two real-time Cortex-M4 microcontroller cores. The Azure Sphere OS runs on the Cortex-A7 core.

Because there are two types of cores, developers can code two types of applications. A high-level application targets the Cortex-A7 and a real-time application, or RTApp, targets one of the Cortex-M4 cores. High-level applications run on the Azure Sphere OS and real-time applications run on bare metal or on a real-time operating system (RTOS).

You can code high-level applications without any real-time code, but all real-time code must be packaged in a high-level application. To be precise, the build process produces a file called an image package, which is deployed to the MT3620.

To demonstrate this, the first section of this article walks through the compilation and deployment of a project that turns a LED on and off. The second section of this article goes into greater depth, and explains how to code and configure a high-level application that controls the LED using a button press.

1. The Blink Project

Building image packages for Azure Sphere is a complex process, so it's a good idea to work with existing projects before you create your own. For this reason, Visual Studio Code provides the Blink project, This section presents the tasks needed to build and deploy this project, and the first task is to prepare the development board.

1.1 Preparing the Development Board

By default, you can't debug applications on the MT3620. To configure the board to run/debug code, you need to enter the following command on the command line:

azsphere device enable-development

This allows high-level applications to be deployed for the device. As I'll explain in a later article, you need to add a configuration flag to enable deployment of real-time applications.

1.2 Creating the Project

To create the Blink project in Visual Studio Code, six steps are needed:

  1. Open the command palette by pressing Ctrl-Shift-P.
  2. Type Azure Sphere in the search box.
  3. Select the Azure Sphere: Generate New Project option.
  4. Another box will display three templates. Choose Blink.
  5. Select a folder where the project's files should be stored.
  6. Press Enter to set Blink as the project's name.

At this point, Visual Studio code will add files and folders to the project. These include the following:

  • HardwareDefinitions - contains files that describe development boards
  • out/ARM-Debug - contains build files
  • app_manifest.json - contains metadata about the project
  • applibs_versions.h - identifies versions of the libraries required by the project
  • CMakeLists.txt - defines the build process
  • main.c - contains the code that causes the LED to blink

CMakeLists.txt is particularly important because it defines the steps needed to build the project. It identifies the project's name (Blink), the programming language (C), the name of the executable (Blink), and the libraries needed to build the executable (libapplibs.so, libpthread.so, and libgcc_s.so). It also identifies the target hardware with the following line:

azsphere_target_hardware_definition(${PROJECT_NAME} 
  TARGET_DIRECTORY "HardwareDefinitions/mt3620_rdb"
  TARGET_DEFINITION "template_appliance.json")

This states that the hardware definition can be found in the HardwareDefinitions/mt3620_rdb folder. This is fine if you're using the Reference Development Board, but if you're not, you need to change this. If you're using Avnet's MT3620 Starter Kit, you'll change mt3620_rdb to avnet_mt3620_sk. If you're using the Mini Development Board from Seeed Studio, you'll change mt3620_rdb to seeed_mt3620_mdb.

1.3 Building the Project

Once you've identified the target hardware, you can build the Blink project by opening the command palette (Ctrl-Shift-P) and executing CMake: Build. The build operation consists of four steps:

  1. Compile main.c for the Cortex-A7 to obtain the object file, main.c.obj.
  2. Link main.c.obj and its required libraries to obtain Blink.out.
  3. Copy Blink.out to a file named app in the project's out/ARM-Debug/bin folder.
  4. Use the azsphere image-package pack-application command to create the image package.

The last step is important to understand. To create an image package, the azsphere image-package pack-application command accepts several flags, including the following:

  • input - the directory containing the compiled source files (*.o)
  • output - the name and location of the image package (*.imagepackage) to be created
  • application-manifest - file that provides information about the project
  • hardware-definitions - file that maps the board's peripherals to values

In this project, the output flag is set to out/ARM-Debug/Blink.imagepackage. As a result, the build process produces a file named Blink.imagepackage in the project's out/ARM-Debug folder.

1.4 Deploying the Image Package

To deploy the image package to the development board, launch a debugging session by pressing F5 or by selecting Run > Start Debugging in the main menu. This deletes applications running on the board and deploys the image package with its required libraries. When deployment is finished, the LED on the board will start flashing on and off.

This process also deploys gdbserver to the development board. This allows you to set breakpoints and step through the project's code as though it was running on your computer.

2. The ButtonPress Project

Now that you can make an LED blink, it's time to look at a custom-coded application that toggles an LED when the user presses a button. The buttonpress.zip file attached to this article contains the ButtonPress project. It's not very exciting, but it's easy to understand and easy to test.

This section explains how the ButtonPress project works. But before I present the source code, I need to explain the capabilities provided by the Applibs library. In particular, I need to explain how to access the board's general-purpose input/output (GPIO) pins.

2.1 Applibs and GPIO

The linking process combines compiled code with prebuilt code files called libraries. In version 11, the SDK provides its libraries for high-level applications in the Sysroots\11\usr\lib folder. The most important library is libapplibs.so, which provides functions that access features of the MT3620 and the development board.

To access these functions, a source file needs to include one or more of the header files in the Sysroots\11\usr\include\applibs directory. There are 34 header files in total and their names identify the types of functions they declare. For example, log.h contains functions for logging, wificonfig.h contains functions that configure WiFi, and adc.h contains functions that access the analog-to-digital converter (ADC).

Each development board provides pins that can be configured to read input and/or write output. These are called general-purpose input/output (GPIO) pins. All of the board's LEDs and buttons are connected to GPIO pins, and to access them, we need to call the functions in gpio.h. Table 1 lists each of these functions.

Table 1: GPIO Functions in gpio.h

Function Name Description
GPIO_OpenAsInput(GPIO_Id, gpioId) Configures a GPIO pin to receive input
GPIO_OpenAsOutput(GPIO_Id, gpioId,
GPIO_OutputMode_Type outputMode,
GPIO_Value_Type initialValue)
Configures a GPIO pin to provide output
GPIO_GetValue(int gpioFd,
GPIO_Value_Type *outValue)
Reads the value of an input pin
GPIO_SetValue(int gpioFd,
GPIO_Value_Type value)
Sets the value of an output pin

The ButtonPress application reads a button's state and uses it to set an LED's state. Therefore, the button's GPIO pin should be configured for input and the LED's GPIO pin should be configured for output. After the pins are configured, the application should call GPIO_GetValue to access the button's state and GPIO_SetValue to set the LED's state.

You can get an idea of how this works by looking at the main.c file in the Blink project. The following code configures the LED pin to serve as an output pin:

C++
GPIO_OpenAsOutput(TEMPLATE_LED, GPIO_OutputMode_PushPull, GPIO_Value_High);

The first argument of GPIO_OpenAsInput and GPIO_OpenAsOutput identifies the pin to be configured. In this code, the pin is identified with TEMPLATE_LED, which is declared in the template_appliance.h header. This header is generated from template_appliance.json, which is the project's hardware definition file.

2.2 Hardware Definition Files

Every project needs a hardware definition file so that source code can access the development board's features. The file's name and location must be set in the azsphere_target_hardware_definition command in CMakeLists.txt. In the Blink project, this command is given as follows:

C++
azsphere_target_hardware_definition(${PROJECT_NAME}
   TARGET_DIRECTORY "HardwareDefinitions/avnet_mt3620_sk"
   TARGET_DEFINITION "template_appliance.json")

TARGET_DIRECTORY sets the directory containing the hardware definition file and TARGET_DEFINITION sets its name. Each hardware definition file is formatted using JSON and defines four properties:

  • Metadata - the file's type and version
  • Description - the board's name and header comments
  • Imports - the SDK's definition file for the development board
  • Peripherals - features of the development board to be accessed in code

The first two properties are straightforward, but the third and fourth deserve explanation. The SDK's installation directory contains a folder named HardwareDefinitions, and this contains a JSON file for each supported development board. These files have the same structure as the project's hardware definition files, so I'll refer to them as low-level definition files.

The Imports property identifies the low-level definition file for the target development board. For example, the file for Avnet's MT3620 Starter Kit is avnet_mt3620_sk.json, so the Imports property must be set as follows:

C++
"Imports" : [ {"Path": "avnet_mt3620_sk.json"} ]

The Peripherals property identifies which features of the development board need to be accessed. This is assigned to an array of elements, and each element has four fields:

  • Name - user-defined ID to be used in source code
  • Type - the peripheral's type (Gpio, PWM, Adc, etc.)
  • Mapping - feature ID defined in the low-level definition file
  • Comments - text to be inserted in code as a comment

The Type and Mapping fields are given in the low-level definition file. For example, avnet_mt3620_sk.json file defines the blue LED in the following way:

C++
{"Name": "AVNET_MT3620_SK_USER_LED_BLUE", "Type": "Gpio",
    "Mapping": "AVNET_AESMS_PIN13_GPIO10", 
    "Comment": "User LED Blue channel uses GPIO10."}

The low-level definition file, avnet_mt3620_sk.json, says that the blue LED pin has type Gpio and a name of AVNET_MT3620_SK_USER_LED_BLUE. To access this pin in code, the project's hardware definition file must contain an entry that maps the LED to a user-defined ID. The following entry shows how this can be done:

C++
{"Name": "BLUE_LED", "Type": "Gpio", 
"Mapping": "AVNET_MT3620_SK_USER_LED_BLUE", 
   "Comment": "Blue LED"}

As shown, the Mapping field in the hardware definition file matches the Name field of the low-level definition file. Also, both entries set the Type to Gpio. As a result, the blue LED can be accessed in source code through the name BLUE_LED.

Another example will clarify how peripherals are accessed. The avnet_mt3620_sk.json file defines the first button (Button A) with the following entry:

C++
{"Name": "AVNET_MT3620_SK_USER_BUTTON_A", "Type": "Gpio",
    "Mapping": "AVNET_AESMS_PIN14_GPIO12", 
    "Comment": "User BUTTON A uses GPIO12."}

To access this in code, the project's hardware definition file must have an entry that maps this button to a user-defined name. The following entry maps the button to the name BUTTON.

C++
{"Name": "BUTTON", "Type": 
"Gpio", ​​"Mapping": "AVNET_MT3620_SK_USER_BUTTON_A", 
   "Comment": "Push button"}

If a project is intended to support multiple development boards, it will need multiple hardware definition files. But only one file can be specified in the azsphere_target_hardware_definition command in CMakeLists.txt.

2.3 Files in the ButtonPress Project

Now that you're familiar with GPIO and hardware definition files, let's look at the ButtonPress project. I created this in Visual Studio Code by pressing Ctrl-Shift-P and executing Azure Sphere: Generate New Project. Then I chose the HLCore Blank template and entered the name ButtonPress.

The generated project contains main.c and CMakeLists.txt, but no hardware definition files. The first part of this discussion explains how to write these files and use them to generate header files. The second part explains how to add code to main.c that turns on the blue LED when Button A is pressed. The last part presents the project's app_manifest.json and compile_commands.json files.

Hardware Definitions

If you open the ButtonPress project in Visual Studio Code, you'll see that the HardwareDefinitions folder has two subfolders:

  • avnet_mt_3620_sk - contains the hardware definition file for the MT3620 Starter Kit from Avnet
  • mt3620_rdb - contains the hardware definition file for the MT3620 Reference Development Board from Seeed Studio

In both of these subfolders, the hardware description file is named hardware.json. The two hardware.json files are nearly identical, and each defines peripherals named BLUE_LED and BUTTON. The only difference is that they refer to different development boards. For example, hardware.json in mt3620_rdb contains the following content:

C++
"Imports" : [ {"Path": "mt3620_rdb.json"} ],
"Peripherals": [
    {"Name": "BLUE_LED", "Type": "Gpio", 
      "Mapping": "MT3620_RDB_LED1_BLUE", "Comment": "Blue LED"},
    {"Name": "BUTTON", "Type": "Gpio", 
      "Mapping": "MT3620_RDB_BUTTON_A", "Comment": "Push button"}
]

A build operation can only accesses one hardware description file, and the file's name and location must be set in CMakeLists.txt. For example, the following command tells CMake to access the header.json file in the HardwareDefinitions/avnet_mt_3620_sk directory.

C++
azsphere_target_hardware_definition(${PROJECT_NAME} 
   TARGET_DIRECTORY "HardwareDefinitions/avnet_mt3620_sk" 
   TARGET_DEFINITION "hardware.json")

Before the project can be built, header files need to be generated from the hardware definition files. This requires four steps:

  1. Create a directory named inc in each folder containing hardware.json.
  2. Create a subfolder named hw in each inc folder.
  3. Open a terminal in Visual Studio Code.
  4. For each directory containing hardware.json, execute the following command in the terminal:
    azsphere hardware-definition generate-header --hardware-definition-file hardware.json

Step 4 produces a file named hardware.h in the inc/hw directory. Each hardware.h file defines a macro named BLUE_LED and a macro named BUTTON. Also, each header has an include statement that accesses the low-level definition file of the development board.

Writing Code

Once hardware.h has been generated for each hardware.json, the source code can be written. The ButtonPress application calls GPIO functions and accesses macros (BLUE_LED and BUTTON) from the hardware description file. Therefore, it needs to include the gpio.h and hardware.h headers. The project's code, contained in main.c, is given as follows:

C++
#include <applibs/gpio.h>
#include <hw/hardware.h>

int main(void) {

    // Configure Button A for input
    int button_descriptor = GPIO_OpenAsInput(BUTTON);

    // Configure the blue LED for output
    int led_descriptor = GPIO_OpenAsOutput(BLUE_LED, 
     GPIO_OutputMode_PushPull, GPIO_Value_High);

   // Store the state of the button
    GPIO_Value_Type button_state;
    while (1) {

        // Determine if the button is pressed
        GPIO_GetValue(button_descriptor, &button_state);

        // Set the LED with the button's state
        GPIO_SetValue(led_descriptor, button_state);
    }
    return 0;
}

This code has two parts. First, it configures the button pin to receive input and the LED pin to provide output. Then it executes an infinite loop that reads the button's value and writes it to the LED.

Application Manifest

By default, applications don't have permission to access features like GPIO or analog-to-digital conversion. Therefore, projects need to identify the features they need in a file named app_manifest.json. This is called the project's application manifest.

When an Azure Sphere project is generated, app_manifest.json will be created and seven of its properties will be set:

  • SchemaVersion - the project's version
  • Name - the project's name
  • ComponentId - a unique identifier for the project
  • EntryPoint - the executable's name and path
  • CmdArgs - arguments to be passed to the executable
  • Capabilities - features required by the executable
  • ApplicationType - the application's type

By default, SchemaVersion is set to 1, Name is set to the project's name, and ComponentId is generated automatically. The default value of EntryPoint is /bin/app. As I'll explain shortly, this means the executable should be renamed app and placed in the project's out/ARM-Debug/bin directory.

The Capabilities property is set to a list of elements that features that the project wants to access. Each element assigns a feature name to a value. Feature names include Gpio, Adc, NetworkConfig, and WifiConfig. For GPIO elements, the name Gpio must be assigned to an array containing the pin names to be accessed. In the ButtonPress manifest, the Capabilities property is set in the following way:

C++
"Capabilities": { "Gpio": [ "$BLUE_LED", "$BUTTON" ] }

By default, the ApplicationType property is set to Default. If the project relies on a custom debugger instead of gdbserver, this should be set to Debugger.

Compilation Commands

When an Azure Sphere project is generated, a file named compile_commands.json is placed in the project's out directory. This provides three pieces of information that are very important for the build process:

  • directory - The absolute path of the project's out/ARM-Debug directory
  • command - The command that builds the project
  • file - The absolute path of the source file containing the main function

As mentioned earlier, the build process accesses the executable as bin/app. The directory property identifies where bin/app can be found. This means bin/app must be located in the project's out/ARM-Debug folder.

On my system, the ButtonPress project is located in C:\test. You'll probably place it in a different directory, so if you want to build the project, be sure to update compile-commands.json with its correct location. This requires changing all three of the properties.

2.4 Building and Deploying ButtonPress

Before you build the ButtonPress project, make sure two tasks are completed:

  • Check CMakeLists.txt to make sure that it identifies the correct hardware definition file
  • Check compile_commands.json to make sure that it contains the project's correct location

Once this is done, you can build the ButtonPress project by opening the command palette (Ctrl-Shift-P) and executing CMake: Build. If this completes successfully, a file named ButtonPress.imagepackage will be present in the project's out/ARM-Debug folder.

To deploy ButtonPress.imagepackage to the development board, press F5 or select Run > Start Debugging. Once deployment is finished, the blue LED should turn on when Button A is pressed. When Button A isn't pressed, the blue LED should stay off.

History

  • 31st October, 2021: Article submitted for publication

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)