部署教程 ‐ Deployment Tutorial - EVAyo/oled-ui-astra GitHub Wiki

简体中文

or English

部署教程

前言

在一切开始之前,您需要确保您的 编译器 ,或者说 开发环境 支持 C++ 编程。

下面将给出一些已明确的支持 C++ 编程的 开发环境

  1. Keil
  2. CLion
  3. Arduino IDE (原生支持 C++ )
  4. STM32CubeIDE
  5. IAR
  6. Eclipse
  7. ...

如果您正在使用上述的某一种 开发环境 ,您还需要通过某些修改,使得您的 开发环境 可以编译并烧录 C++ 程序到您的硬件平台。

下面给出使用 CLion + STM32 的一个范例教程

如果您使用的是其他 开发环境 或者 硬件平台 , 可以自行检索具体的方法。相信我,如果您可以看到这篇文章,那您就一定可以做好这一步。

进行了上述操作后,如果您的 开发环境 并非 原生支持C++ 。您还需要编写以下两个文件,放入工程文件夹内。

请注意,在 astra UI 发行版的代码压缩包内,包含了 astra_rocket.hastra_rocket.cpp 文件,是笔者已经编写好的引导文件,分别对应下文中的 test.htest.cpp ,您可以直接使用它们。

//test.h

#ifndef __TEST_H_
#define __TEST_H_
#ifdef __cplusplus
extern "C" {
#endif

/*---c---*/
#include "main.h"
#include "gpio.h"

//此处添加其它C头文件

void CppMain();  //主程序函数

#ifdef __cplusplus
}

/*---c++---*/
#include "LED.h"

//此处添加其他Cpp头文件

#endif
#endif
//test.cpp

//主程序入口
void CppMain() {
  for(;;) {
    //主循环
  }
}

最后一步,您需要在您的 main.c 中包含您编写好的头文件,并按下文方式调用您编写好的引导函数。

//main.c

#include "test.h"

.
.
.

CppMain();  
//在进入原本的主循环之前调用该函数 接管程序

while(1) {
  //弃用
}

万事开头难,您已经做完了最难的一步,接下来让我们开始吧!

关于 HAL

特别感谢 @Forairaaaaa ,笔者基于其开源的 monica ,学习到了很多相关知识。

在正式开始部署教程前,笔者需要先向您介绍 astra UI 配套的 HAL ,即 硬件抽象层

astra UI 中,系统配置、屏幕驱动、图形绘制、I/O口驱动和生成随机数都集成在了 HAL 中。

HAL 保证了 astra UI 的运行不受硬件平台的限制。 同时,您绝对不会在任何 astra UI 的代码文件中看到任何一行与硬件平台有关的代码。digitalWrite()HAL_GPIOWritePin() 等。

例如,当您想让系统延时500ms时,您无需执行 delay(500); (Arduino)HAL_Delay(500); (STM32) ,您只需要执行 HAL::Delay(500); 即可。前者的代码不能在不同平台执行,也非常不易于移植,但采用了 HAL 的后者,与硬件平台无关,可以在任何平台执行。

这使得 astra UI 几乎可以在任何主流硬件平台上部署和运行,只需要您针对您自己的硬件平台 重载 对应的 HAL 函数即可。

若您还是有些迷惑,篇幅所限,笔者在这里附上一部分 HAL 类的原型代码:

class HAL {
private:
  static HAL *hal;

public:
  static HAL *get();    //get hal instance.
  static bool check();  //check if there is a hal instance.

  static bool inject(HAL *_hal);  //inject HAL instance and run hal_init.
  static void destroy();  //destroy HAL instance.

  virtual ~HAL() = default;

  virtual std::string type() { return "Base"; }

  virtual void init() {}

protected:
  sys::config config;

public:
  static void canvasUpdate() { get()->_canvasUpdate(); }
  virtual void _canvasUpdate() {}

  static void canvasClear() { get()->_canvasClear(); }
  virtual void _canvasClear() {}

  static void drawPixel(float _x, float _y) { get()->_drawPixel(_x, _y); }
  virtual void _drawPixel(float _x, float _y) {}

  static void drawEnglish(float _x, float _y, const std::string &_text) { get()->_drawEnglish(_x, _y, _text); }
  virtual void _drawEnglish(float _x, float _y, const std::string &_text) {}

  static void drawChinese(float _x, float _y, const std::string &_text) { get()->_drawChinese(_x, _y, _text); }
  virtual void _drawChinese(float _x, float _y, const std::string &_text) {}

  /**
   * @brief system timers.
   */
public:
  static void delay(unsigned long _mill) { get()->_delay(_mill); }
  virtual void _delay(unsigned long _mill) {}

  //...
};

可以发现, HAL 是由很多组函数构成,分别是一个静态成员函数和一个名字以下划线开头的虚成员函数。

每组函数中,静态成员函数的任务就是调用 get() 方法,获取到当前存储在 HAL 类中的 HAL 实例(也就是下文中您自己编写的派生 HAL )。而后调用 HAL 实例中的相关方法。

下面是 inject() 方法的实现,它用于将派生 HAL 注入回 HAL 。并将派生 HAL 指针存储于 HAL 中。

bool HAL::inject(HAL *_hal) {
  if (_hal == nullptr) {
    return false;
  }

  _hal->init();
  hal = _hal;
  return true;
}

所以不难发现,您需要做的事情有三个:

  1. 继承此 HAL 类,编写属于您自己硬件平台的派生 HAL
  2. 在派生 HAL 中重写所有您需要用到的虚函数
  3. 将您编写的派生 HAL 通过 inject() 方法注入回 HAL

下面,笔者将以更详尽的描述,带您一步步部署 astra UI 到您的硬件平台。

第一步:编写派生 HAL

编写框架

首先您需要在您的 .h 文件中包含 hal.h 头文件。

继承并编写派生 HAL ,同时给您的派生 HAL 命名,这里以 MyHAL 为例,下文中都用 MyHAL 指代派生 HAL

顺带编写一个默认的构造函数。 ^_^

//MyHAL.h

#pragma once
#ifndef MYHAL_H_
#define MYHAL_H_

#include "hal.h"

class MyHAL : public HAL {
public:
  MyHAL() = default;
};

#endif

编写 MyHAL_xx_init() 方法并重写 init 方法

这一步是因人而异的,取决于您的硬件平台和您接入的各种设备。

init() 方法会在您调用 HAL::inject() 方法时自动被调用。

_xx_init() 方法仅存在于您的派生 HAL 中,并应该在您派生类的 init() 方法中调用。

其用于初始化您的所有个性化设备。

请注意,如果您需要加入图形库(笔者也推荐您这么做),请同时也在 MyHAL.h 中包含您图形库的 .h 文件,并为其编写 xx_init() 方法。

下面以笔者使用的 STM32u8g2 图形库为例,给出一段代码:

//MyHAL.h

class MyHAL : public HAL {
private:
  void _stm32_hal_init();
  void _sys_clock_init();
  void _gpio_init();
  void _dma_init();
  void _timer_init();
  void _spi_init();
  void _adc_init();

  void _ssd1306_init();
  void _key_init();
  void _buzzer_init();
  void _u8g2_init();

public:
  inline void init() override {
    _stm32_hal_init();
    _sys_clock_init();
    _gpio_init();
    _dma_init();
    _timer_init();
    _spi_init();
    _adc_init();

    _ssd1306_init();
    _key_init();
    _buzzer_init();
    _u8g2_init();
  }
};

再给出上面某些函数的实现,供您参考:

//MyHAL_STM32.cpp

void MyHAL::_gpio_init() {
  MX_GPIO_Init();
}

void MyHAL::_adc_init() {
  MX_ADC1_Init();
}

void MyHAL::_spi_init() {
  MX_SPI2_Init();
}

请注意,这些方法的实现需要在 MyHAL.cpp 文件中编写,当然,笔者建议您把不同种类的设备放在不同的 .cpp 文件中。

MyHAL_key.cppMyHAL_oled.cppMyHAL_STM32.cpp 等。

重写其他方法

接下来是重头戏。

您需要根据您选择的图形库(或者在不借助图形库的情况下),重写绘制像素、绘制直线、绘制矩形等一系列方法。

您需要根据您的屏幕,重写刷新画布、清空屏幕等一系列方法。

您需要根据您的硬件平台,重写延时、获取开机时间、生成真随机数等一系列方法。

您需要根据您的外设类型,重写蜂鸣器发声、按键扫描等一系列方法。

当然,如果您明确您的需求,可以选择不重写某些方法,使用 HAL 父类中默认的方法实现。

比如,如果您不需要生成真随机数,您就无需重写 _getRandomSeed() 方法,或者直接让其返回一个固定值即可。

比如,您不需或没有蜂鸣器,您就无需重写 _beep() 及其一系列方法。

下面是笔者所有重写了的函数,供您参考,完整的可以重写的函数列表,您可以查阅 hal.h

//MyHAL.h

class MyHAL : public HAL {
protected:
  u8g2_t canvasBuffer {};

public:
  void _screenOn() override;
  void _screenOff() override;

public:
  void* _getCanvasBuffer() override;
  uint8_t _getBufferTileHeight() override;
  uint8_t _getBufferTileWidth() override;
  void _canvasUpdate() override;
  void _canvasClear() override;
  void _setFont(const uint8_t * _font) override;
  uint8_t _getFontWidth(std::string& _text) override;
  uint8_t _getFontHeight() override;
  void _setDrawType(uint8_t _type) override;
  void _drawPixel(float _x, float _y) override;
  void _drawEnglish(float _x, float _y, const std::string& _text) override;
  void _drawChinese(float _x, float _y, const std::string& _text) override;
  void _drawVDottedLine(float _x, float _y, float _h) override;
  void _drawHDottedLine(float _x, float _y, float _l) override;
  void _drawVLine(float _x, float _y, float _h) override;
  void _drawHLine(float _x, float _y, float _l) override;
  void _drawBMP(float _x, float _y, float _w, float _h, const uint8_t* _bitMap) override;
  void _drawBox(float _x, float _y, float _w, float _h) override;
  void _drawRBox(float _x, float _y, float _w, float _h, float _r) override;
  void _drawFrame(float _x, float _y, float _w, float _h) override;
  void _drawRFrame(float _x, float _y, float _w, float _h, float _r) override;

public:
  void _delay(unsigned long _mill) override;
  unsigned long _millis() override;
  unsigned long _getTick() override;
  unsigned long _getRandomSeed() override;

public:
  void _beep(float _freq) override;
  void _beepStop() override;
  void _setBeepVol(uint8_t _vol) override;

public:
  bool _getKey(key::KEY_INDEX _keyIndex) override;

public:
  void _updateConfig() override;
};

同时,下面以笔者使用的 STM32u8g2 图形库为例,给出一段重写的函数实现代码:

//MyHAL_oled.cpp

void HALDreamCore::_drawPixel(float _x, float _y) {
  u8g2_DrawPixel(&canvasBuffer, (int16_t)std::round(_x), (int16_t)std::round(_y));
}

void HALDreamCore::_drawChinese(float _x, float _y, const std::string &_text) {
  u8g2_DrawUTF8(&canvasBuffer, (int16_t)std::round(_x), (int16_t)std::round(_y), _text.c_str());
}

void HALDreamCore::_drawVDottedLine(float _x, float _y, float _h) {
  for (uint8_t i = 0; i < (uint8_t)std::round(_h); i++) {
    if (i % 8 == 0 | (i - 1) % 8 == 0 | (i - 2) % 8 == 0) continue;
    u8g2_DrawPixel(&canvasBuffer, (int16_t)std::round(_x), (int16_t)std::round(_y) + i);
  }
}

注意事项

在编写 MyHAL 的过程中,如果需要额外的自定义函数,请直接在 MyHAL 类中声明 protected 类型的成员函数。

至此,派生 HAL 编写完成。

第二步:注入派生 HAL

在您的引导程序的初始化部分,编写如下代码(笔者假设您已经包含了 MyHAL.h ):

HAL::inject(new MyHAL);

至此,您已经可以调用 HAL 中对应的静态成员函数了。关于 HAL 的程序皆已编写完毕。

下一步,运行 HAL 测试程序,测试您的代码是否正确。

第三步:运行 HAL 测试程序

您可以在注入派生 HAL 后运行一段简单的测试程序,来检验您代码的正确性。

HAL::delay(100);
HAL::printInfo("loading...");
HAL::delay(500);
HAL::printInfo("astra UI by dcfsswindy.");
HAL::delay(100);

如果屏幕中出现了如下的提示信息,恭喜您! HAL 部署成功。

CPT2404021245-228x114

测试 astra UI

一般情况下, HAL 部署完成后,astra UI 就已经可以正常工作了。

但还是建议您运行下方的测试程序,用于测试 astra UI 是否正常工作(假设您已经正确包含了相关头文件)。

//初始化部分
void setup() {
  astra::Launcher* astraLauncher = new astra::Launcher();
  astra::Menu* rootPage = new astra::Menu("root");

  rootPage->addItem(new astra::Menu("test1"));

  astraLauncher->init(rootPage);

  astra::drawLogo(1000);
}

//主循环部分
void loop() {
  astraLauncher->update();
}

若一切正常,您的屏幕将在显示 astra UI 开机动画后进入一个只有一个子元素 test1 的菜单。

至此, astra UI 部署完毕。

English

或者 简体中文

Deployment tutorial

Preface

Before anything else, you need to make sure that your compiler , or development environment , supports C++ programming.

The following are some of the development environments that explicitly support C++ programming:

  1. Keil
  2. CLion
  3. Arduino IDE (natively supports C++ )
  4. STM32CubeIDE
  5. IAR
  6. Eclipse
  7. ...

If you are using one of the above development environments , you will need to make some modifications so that your development environment can compile and burn C++ programs to your hardware platform.

An example tutorial using CLion + STM32 click here.

If you are using another development environment or hardware platform, you can find out how to do it yourself. **Trust me, if you can read this article, you can do this step. **

After doing the above, if your development environment is not natively supporting C++ . You will also need to write the following two files and put them in the project folder.

Please note that the astra_rocket.h and astra_rocket.cpp files included in the astra UI distribution's code zip are bootstrap files that the author has already written, corresponding to test.h and test.cpp below, and which you can use directly.

//test.h

#ifndef __TEST_H_
#define __TEST_H_
#ifdef __cplusplus
extern "C" {
#endif

/*---c---*/
#include "main.h"
#include "gpio.h"

//add other c header here.

void CppMain();  //main program method.

#ifdef __cplusplus
}

/*---c++---*/
#include "LED.h"

//add other cpp header here.

#endif
#endif
//test.cpp

//main program
void CppMain() {
  for(;;) {
    //main loop
  }
}

As a final step, you need to include the header file you wrote in your main.c and call the bootstrap function you wrote as follows.

//main.c

#include "test.h"

.
.
.

CppMain();  
//Call this function to take over the program before entering the original main loop.

while(1) {
  //deprecated.
}

It's hard to start everything, and you've already done the hardest step, so let's get started next!

About HAL

Special thanks to @Forairaaaaa, I have learned a lot about monica based on his open source monica .

Before we start the deployment tutorial, I would like to introduce you to the astra UI companion HAL , the Hardware Abstraction Layer .

In astra UI , system configuration, screen driver, graphics drawing, I/O port driver and random number generation are all integrated in HAL .

The HAL ensures that the astra UI runs regardless of the hardware platform. At the same time, you will never see a single line of hardware-related code in any astra UI code file. Such as digitalWrite() or HAL_GPIOWritePin() .

For example, when you want to delay the system by 500ms, you don't need to execute delay(500); (Arduino) or HAL_Delay(500); (STM32) , you just need to execute HAL::Delay(500); . The former code cannot be executed on different platforms and is very non-portable, but the latter with HAL is hardware platform independent and can be executed on any platform.

This allows astra UI to be deployed and run on almost any major hardware platform, you just need to overload the corresponding HAL functions for your hardware platform.

If you are still a bit confused, due to the limitation of space, I attach part of the prototype code of HAL class here:

class HAL {
private:
  static HAL *hal;

public:
  static HAL *get();    //get hal instance.
  static bool check();  //check if there is a hal instance.

  static bool inject(HAL *_hal);  //inject HAL instance and run hal_init.
  static void destroy();  //destroy HAL instance.

  virtual ~HAL() = default;

  virtual std::string type() { return "Base"; }

  virtual void init() {}

protected:
  sys::config config;

public:
  static void canvasUpdate() { get()->_canvasUpdate(); }
  virtual void _canvasUpdate() {}

  static void canvasClear() { get()->_canvasClear(); }
  virtual void _canvasClear() {}

  static void drawPixel(float _x, float _y) { get()->_drawPixel(_x, _y); }
  virtual void _drawPixel(float _x, float _y) {}

  static void drawEnglish(float _x, float _y, const std::string &_text) { get()->_drawEnglish(_x, _y, _text); }
  virtual void _drawEnglish(float _x, float _y, const std::string &_text) {}

  static void drawChinese(float _x, float _y, const std::string &_text) { get()->_drawChinese(_x, _y, _text); }
  virtual void _drawChinese(float _x, float _y, const std::string &_text) {}

  /**
   * @brief system timers.
   */
public:
  static void delay(unsigned long _mill) { get()->_delay(_mill); }
  virtual void _delay(unsigned long _mill) {}

  //...
};

As you can see, HAL is made up of a number of groups of functions, a static member function and a dummy member function whose name begins with an underscore.

In each group, the static member function's task is to call the get() method to get the current instance of HAL stored in the HAL class (i.e., the derived HAL that you write below). You then call the relevant methods in the HAL instance.

Here is the implementation of the inject() method, which is used to inject the derived HAL back into the HAL . and stores the derived HAL pointer in HAL .

bool HAL::inject(HAL *_hal) {
  if (_hal == nullptr) {
    return false;
  }

  _hal->init();
  hal = _hal;
  return true;
}

So it's easy to see that there are three things you need to do:

  1. Inherit this HAL class and write your own hardware platform derived HAL
  2. rewrite all the virtual functions you need to use in the derived HAL
  3. inject your derived HAL back into HAL via inject() method

In the following, I will take you step by step to deploy astra UI to your hardware platform with more detailed description.

The first step: writing the derived HAL

Writing the Framework

First you need to include the hal.h header file in your .h file.

Inherit and write a derived HAL, and give your derived HAL a name, here MyHAL for example, hereinafter all use MyHAL to refer to the derived HAL .

Incidentally, write a default constructor. ^_^

//MyHAL.h

#pragma once
#ifndef MYHAL_H_
#define MYHAL_H_

#include "hal.h"

class MyHAL : public HAL {
public:
  MyHAL() = default;
};

#endif

Write the _xx_init() method of MyHAL and override the init method

This step varies from person to person, depending on your hardware platform and the various devices you have access to.

The init() method is automatically called when you call the HAL::inject() method.

The _xx_init() method exists only in your derived HAL and should be called in the init() method of your derived class.

It is used to initialize all your personalization devices.

Please note that if you need to include a graphics library (and I recommend you do), please also include the .h file for your graphics library in MyHAL.h and write the xx_init() method for it.

The following code is an example of the STM32 and u8g2 graphics libraries that I use:

//MyHAL.h

class MyHAL : public HAL {
private:
  void _stm32_hal_init();
  void _sys_clock_init();
  void _gpio_init();
  void _dma_init();
  void _timer_init();
  void _spi_init();
  void _adc_init();

  void _ssd1306_init();
  void _key_init();
  void _buzzer_init();
  void _u8g2_init();

public:
  inline void init() override {
    _stm32_hal_init();
    _sys_clock_init();
    _gpio_init();
    _dma_init();
    _timer_init();
    _spi_init();
    _adc_init();

    _ssd1306_init();
    _key_init();
    _buzzer_init();
    _u8g2_init();
  }
};

The implementation of some of the above functions is given again for your reference:

//MyHAL_STM32.cpp

void MyHAL::_gpio_init() {
  MX_GPIO_Init();
}

void MyHAL::_adc_init() {
  MX_ADC1_Init();
}

void MyHAL::_spi_init() {
  MX_SPI2_Init();
}

Please note that the implementation of these methods needs to be written in the MyHAL.cpp file, but of course, I recommend that you put different kinds of devices in different .cpp files.

Of course, I recommend you to put different kinds of devices in different .cpp files, such as MyHAL_key.cpp , MyHAL_oled.cpp and MyHAL_STM32.cpp .

Rewriting other methods

Next comes the heavy lifting.

Depending on your graphics library of choice (or without the aid of a graphics library), You need to override draw pixels, draw lines, draw rectangles, and a host of other methods.

You need to override the methods for refreshing the canvas, clearing the screen, etc., depending on your screen.

You need to override a series of methods such as delay, get boot time, generate true random number, etc. according to your hardware platform.

According to your peripheral type, you need to rewrite a series of methods such as buzzer sound, key scanning and so on.

Of course, if you are clear about your needs, you can choose not to rewrite some methods and use the default method implementations in the HAL parent class.

For example, if you don't need to generate true random numbers, you don't need to override the _getRandomSeed() method, or just have it return a fixed value.

For example, if you don't need or have a buzzer, you don't need to rewrite _beep() and its set of methods.

For reference, here are all the functions that I have rewritten. For a complete list of functions that can be rewritten, see hal.h .

//MyHAL.h

class MyHAL : public HAL {
protected:
  u8g2_t canvasBuffer {};

public:
  void _screenOn() override;
  void _screenOff() override;

public:
  void* _getCanvasBuffer() override;
  uint8_t _getBufferTileHeight() override;
  uint8_t _getBufferTileWidth() override;
  void _canvasUpdate() override;
  void _canvasClear() override;
  void _setFont(const uint8_t * _font) override;
  uint8_t _getFontWidth(std::string& _text) override;
  uint8_t _getFontHeight() override;
  void _setDrawType(uint8_t _type) override;
  void _drawPixel(float _x, float _y) override;
  void _drawEnglish(float _x, float _y, const std::string& _text) override;
  void _drawChinese(float _x, float _y, const std::string& _text) override;
  void _drawVDottedLine(float _x, float _y, float _h) override;
  void _drawHDottedLine(float _x, float _y, float _l) override;
  void _drawVLine(float _x, float _y, float _h) override;
  void _drawHLine(float _x, float _y, float _l) override;
  void _drawBMP(float _x, float _y, float _w, float _h, const uint8_t* _bitMap) override;
  void _drawBox(float _x, float _y, float _w, float _h) override;
  void _drawRBox(float _x, float _y, float _w, float _h, float _r) override;
  void _drawFrame(float _x, float _y, float _w, float _h) override;
  void _drawRFrame(float _x, float _y, float _w, float _h, float _r) override;

public:
  void _delay(unsigned long _mill) override;
  unsigned long _millis() override;
  unsigned long _getTick() override;
  unsigned long _getRandomSeed() override;

public:
  void _beep(float _freq) override;
  void _beepStop() override;
  void _setBeepVol(uint8_t _vol) override;

public:
  bool _getKey(key::KEY_INDEX _keyIndex) override;

public:
  void _updateConfig() override;
};

Meanwhile, a rewritten function implementation code is given below as an example of the STM32 and u8g2 graphic libraries used by the author:

//MyHAL_oled.cpp

void HALDreamCore::_drawPixel(float _x, float _y) {
  u8g2_DrawPixel(&canvasBuffer, (int16_t)std::round(_x), (int16_t)std::round(_y));
}

void HALDreamCore::_drawChinese(float _x, float _y, const std::string &_text) {
  u8g2_DrawUTF8(&canvasBuffer, (int16_t)std::round(_x), (int16_t)std::round(_y), _text.c_str());
}

void HALDreamCore::_drawVDottedLine(float _x, float _y, float _h) {
  for (uint8_t i = 0; i < (uint8_t)std::round(_h); i++) {
    if (i % 8 == 0 | (i - 1) % 8 == 0 | (i - 2) % 8 == 0) continue;
    u8g2_DrawPixel(&canvasBuffer, (int16_t)std::round(_x), (int16_t)std::round(_y) + i);
  }
}

Notes

In the process of writing MyHAL , if you need extra customized functions, please declare protected type member functions in MyHAL class directly.

At this point, the derivation of HAL is complete.

Step 2: Injecting the derived HAL

In the initialization section of your bootloader, write the following code (I assume you have included MyHAL.h ):

HAL::inject(new MyHAL);

At this point, you are ready to call the corresponding static member functions of HAL. The program for HAL has been written.

Next, run the HAL test program to see if your code is correct.

Step 3: Run the HAL test program

You can run a simple test program after injecting the derived HAL to check the correctness of your code.

HAL::delay(100);
HAL::printInfo("loading...");
HAL::delay(500);
HAL::printInfo("astra UI by dcfsswindy.");
HAL::delay(100);

If the following message appears in the screen, congratulations! HAL deployment was successful.

CPT2404021245-228x114

Testing astra UI

Normally, astra UI is ready to work once HAL has been deployed.

However, it is recommended that you run the test program below to test that astra UI is working (assuming you have included the headers correctly).

void setup() {
  astra::Launcher* astraLauncher = new astra::Launcher();
  astra::Menu* rootPage = new astra::Menu("root");

  rootPage->addItem(new astra::Menu("test1"));

  astraLauncher->init(rootPage);

  astra::drawLogo(1000);
}

void loop() {
  astraLauncher->update();
}

If all is well, your screen will enter a menu with only one child element, test1, after displaying the astra UI startup animation.

**This completes the deployment of astra UI . **