HOME
Shop
  • English
  • 简体中文
HOME
Shop
  • English
  • 简体中文
  • Product Series

    • FPGA+ARM

      • GM-3568JHF

        • 1. Introduction

          • About GM-3568JHF
        • 2. Quick Start

          • 00 Introduction
          • 01 Environment Setup
          • 02 Compilation Instructions
          • 03 Flashing Guide
          • 04 Debug Tools
          • 05 Software Update
          • 06 View Information
          • 07 Test Commands
          • 08 App Compilation
          • 09 Source Code Acquisition
        • 3. Peripherals and Interfaces

          • 01 USB
          • 02 Display and Touch
          • 03 Ethernet
          • 04 WIFI
          • 05 Bluetooth
          • 06 TF-Card
          • 07 Audio
          • 08 Serial Port
          • 09 CAN
          • 10 RTC
        • 4. Application Development

          • 01 UART read and write case
          • 02 Key detection case
          • 03 LED light flashing case
          • 04 MIPI screen detection case
          • 05 Read USB device information example
          • 06 FAN Detection Case
          • 07 FPGA FSPI Communication Case
          • 08 FPGA DMA read and write case
          • 09 GPS debugging case
          • 10 Ethernet Test Cases
          • 11 RS485 reading and writing examples
          • 12 FPGA IIC read and write examples
          • 13 PN532 NFC card reader case
          • 14 TF card reading and writing case
        • 5. QT Development

          • 01 ARM64 cross compiler environment construction
          • 02 QT program added automatic startup service
        • 6. RKNN_NPU Development

          • 01 RK3568 NPU Overview
          • 02 Development Environment Setup
          • Run Official YOLOv5 Example
          • Model Conversion Detailed Explanation
          • Run Custom Model on Board
        • 7. FPGA Development

          • ARM and FPGA Communication
          • /fpga-arm/GM-3568JHF/FPGA/ch02-FPGA-Development-Manual.html
        • 8. Others

          • 01 Modification of the root directory file system
          • 02 System auto-start service
        • 9. Download

          • Download Resources
    • ShimetaPi

      • M4-R1

        • 1. Introduction

          • 1.1 About M4-R1
        • 2. Quick Start

          • 2.1 OpenHarmony Overview
          • 2.2 Image Burning
          • 2.3 Development Environment Preparation
          • 2.4 Hello World Application
        • 3. Application Development

          • 3.1 Getting Started

            • 3.1.1 ArkTS Language Overview
            • 3.1.2 UI Components (Part 1)
            • 3.1.3 UI Components (Part 2)
            • 3.1.4 UI Components (Part 3)
          • 3.2 Advanced

            • 3.2.1 Getting Started Guide
            • 3.2.2 Usage of Third Party Libraries
            • 3.2.3 Deployment of the Application
            • 3.2.4 Factory Reset
            • 3.2.5 System Debug
            • 3.2.6 APP Stability Testing
            • 3.2.7 Application Testing
          • 3.3 Getting Docs

            • 3.3.1 Official Website Information
          • 3.4 Development Instructions

            • 3.4.1 Full SDK
            • 3.4.2 Introduction of Third Party Libraries
            • 3.4.3 Introduction of HDC Tool
            • 3.4.4 Restore Factory Mode
            • 3.4.5 Update System API
          • 3.5 First Application

            • 3.5.1 First ArkTS App
          • 3.6 Application Demo

            • 3.6.1 UART Tool
            • 3.6.2 Graphics Tablet
            • 3.6.3 Digital Clock
            • 3.6.4 WIFI Tool
        • 4. Device Development

          • 4.1 Ubuntu Environment Development

            • 4.1.1 Environment Setup
            • 4.1.2 Download Source Code
            • 4.1.3 Compile Source Code
          • 4.2 Using DevEco Device Tool

            • 4.2.1 Tool Introduction
            • 4.2.2 Environment Construction
            • 4.2.3 Import SDK
            • 4.2.4 Function Introduction
        • 5. Peripherals and Interfaces

          • 5.1 Raspberry Pi Interfaces
          • 5.2 GPIO Interface
          • 5.3 I2C Interface
          • 5.4 SPI Communication
          • 5.5 PWM Control
          • 5.6 Serial Port Communication
          • 5.7 TF Card Slot
          • 5.8 Display Screen
          • 5.9 Touch Screen
          • 5.10 Audio
          • 5.11 RTC
          • 5.12 Ethernet
          • 5.13 M.2
          • 5.14 MINI PCIE
          • 5.15 Camera
          • 5.16 WIFI BT
          • 5.17 HAT
        • 6. FAQ

          • 6.1 Download Link
      • M5-R1

        • 1. Introduction

          • M5-R1 Development Documentation
        • 2. Quick Start

          • OpenHarmony Overview
          • Image Burning
          • Development Environment Preparation
          • Hello World Application and Deployment
        • 3. Peripherals and Interfaces

          • 3.1 Raspberry Pi Interfaces
          • 3.2 GPIO Interface
          • 3.3 I2C Interface
          • 3.4 SPI Communication
          • 3.5 PWM Control
          • 3.6 Serial Port Communication
          • 3.7 TF Card Slot
          • 3.8 Display Screen
          • 3.9 Touch Screen
          • 3.10 Audio
          • 3.11 RTC
          • 3.12 Ethernet
          • 3.13 M.2
          • 3.14 MINI PCIE
          • 3.15 Camera
          • 3.16 WIFI BT
          • 3.17 HAT
        • 4. Application Development

          • 4.1 Getting Started

            • 4.1.1 ArkTS Language Overview
            • 4.1.2 UI Components (Part 1)
            • 4.1.3 UI Components (Part 2)
            • 4.1.4 UI Components (Part 3)
          • 4.2 Advanced

            • 4.2.1 Getting Started Guide
            • 4.2.2 Usage of Third Party Libraries
            • 4.2.3 Deployment of the Application
            • 4.2.4 Factory Reset
            • 4.2.5 System Debug
            • 4.2.6 APP Stability Testing
            • 4.2.7 Application Testing
        • 5. Device Development

          • 5.1 Environment Setup
          • 5.2 Download Source Code
          • 5.3 Compile Source Code
        • 6. Download

          • Data Download
    • OpenHarmony

      • SC-3568HA

        • 1. Introduction

          • 1.1 About SC-3568HA
        • 2. Quick Start

          • 2.1 OpenHarmony Overview
          • 2.2 Image Burning
          • 2.3 Development Environment Preparation
          • 2.4 Hello World Application
        • 3. Application Development

          • 3.1 ArkUI

            • 3.1.1 ArkTS Language Overview
            • 3.1.2 UI Components (Part 1)
            • 3.1.3 UI Components (Part 2)
            • 3.1.4 UI Components (Part 3)
          • 3.2 Advanced

            • 3.2.1 Getting Started Guide
            • 3.2.2 Usage of Third Party Libraries
            • 3.2.3 Deployment of the Application
            • 3.2.4 Factory Reset
            • 3.2.5 System Debug
            • 3.2.6 APP Stability Testing
            • 3.2.7 Application Testing
        • 4. Device Development

          • 4.1 Environment Setup
          • 4.2 Download Source Code
          • 4.3 Compile Source Code
        • 5. Peripherals and Interfaces

          • 5.1 Raspberry Pi Interfaces
          • 5.2 GPIO Interface
          • 5.3 I2C Interface
          • 5.4 SPI Communication
          • 5.5 PWM Control
          • 5.6 Serial Port Communication
          • 5.7 TF Card Slot
          • 5.8 Display Screen
          • 5.9 Touch Screen
          • 5.10 Audio
          • 5.11 RTC
          • 5.12 Ethernet
          • 5.13 M.2
          • 5.14 MINI PCIE
          • 5.15 Camera
          • 5.16 WIFI BT
          • 5.17 HAT
        • 6. FAQ

          • 6.1 Download Link
      • M-K1HSE

        • 1. Introduction

          • 1.1 Product Introduction
        • 2. Quick Start

          • 2.1 Debug Tool Installation
          • 2.2 Development Environment Setup
          • 2.3 Source Code Download
          • 2.4 Build Instructions
          • 2.5 Flashing Guide
          • 2.6 APT Update Sources
          • 2.7 View Board Info
          • 2.8 CLI LED and Key Test
          • 2.9 GCC Build Programs
        • 3. Application Development

          • 3.1 Basic Application Development

            • 3.1.1 Development Environment Preparation
            • 3.1.2 First Application HelloWorld
            • 3.1.3 Develop HAR Package
          • 3.2 Peripheral Application Cases

            • 3.2.1 UART Read/Write
            • 3.2.2 Key Demo
            • 3.2.3 LED Flash
        • 4. Peripherals and Interfaces

          • 4.1 Standard Peripherals

            • 4.1.1 USB
            • 4.1.2 Display and Touch
            • 4.1.3 Ethernet
            • 4.1.4 WIFI
            • 4.1.5 Bluetooth
            • 4.1.6 TF Card
            • 4.1.7 Audio
            • 4.1.8 Serial Port
            • 4.1.9 CAN
            • 4.1.10 RTC
          • 4.2 Interfaces

            • 4.2.1 Audio
            • 4.2.2 RS485
            • 4.2.3 Display
            • 4.2.4 Touch
        • 5. System Customization Development

          • 5.1 System Porting
          • 5.2 System Customization
          • 5.3 Driver Development
          • 5.4 System Debugging
          • 5.5 OTA Upgrade
        • 6. Download

          • 6.1 Download
    • EVS-Camera

      • CF-NRS1

        • 1. Introduction

          • 1.1 About CF-NRS1
          • 1.2 Event-Based Concepts
          • 1.3 Quick Start
          • 1.4 Resources
        • 2. Development

          • 2.1 Development Overview

            • 2.1.1 Shimetapi Hybrid Camera SDK Introduction
          • 2.2 Environment & API

            • 2.2.1 Environment Overview
            • 2.2.2 Development API Overview
          • 2.3 Linux Development

            • 2.3.1 Linux SDK Introduction
            • 2.3.2 Linux SDK API
            • 2.3.3 Linux Algorithm
            • 2.3.4 Linux Algorithm API
          • 2.4 Service & Web

            • 2.4.1 EVS Server
            • 2.4.2 Time Server
            • 2.4.3 EVS Web
        • 3. Download

          • 3.1 Download
        • 4. Common Problems

          • 4.1 Common Problems
      • CF-CRA2

        • 1. Introduction

          • 1.1 About CF-CRA2
        • 2. Download

          • 2.1 Download
      • EVS Module

        • 1. Related Concepts
        • 2. Hardware Preparation and Environment Configuration
        • 3. Example Program User Guide
        • Resources Download
    • AI-model

      • 1684XB-32T

        • 1. Introduction

          • AIBOX-1684XB-32 Introduction
        • 2. Quick Start

          • First time use
          • Network Configuration
          • Disk usage
          • Memory allocation
          • Fan Strategy
          • Firmware Upgrade
          • Cross-Compilation
          • Model Quantization
        • 3. Application Development

          • 3.1 Development Introduction

            • Sophgo SDK Development
            • SOPHON-DEMO Introduction
          • 3.2 Large Language Models

            • Deploying Llama3 Example
            • /ai-model/AIBOX-1684XB-32/application-development/LLM/Sophon_LLM_api_server-Development-AIBOX-1684XB-32.html
            • /ai-model/AIBOX-1684XB-32/application-development/LLM/MiniCPM-V-2_6-AIBOX-1684XB-32.html
            • /ai-model/AIBOX-1684XB-32/application-development/LLM/Qwen-2-5-VL-demo-Development-AIBOX-1684XB-32.html
            • /ai-model/AIBOX-1684XB-32/application-development/LLM/Qwen-3-chat-demo-Development-AIBOX-1684XB-32.html
            • /ai-model/AIBOX-1684XB-32/application-development/LLM/Qwen3-Qwen Agent-MCP.html
            • /ai-model/AIBOX-1684XB-32/application-development/LLM/Qwen3-langchain-AI Agent.html
          • 3.3 Deep Learning

            • ResNet (Image Classification)
            • LPRNet (License Plate Recognition)
            • SAM (Universal Image Segmentation Foundation Model)
            • YOLOv5 (Object Detection)
            • OpenPose (Human Keypoint Detection)
            • PP-OCR (Optical Character Recognition)
        • 4. Download

          • Resource Download
      • 1684X-416T

        • 1. Introduction

          • AIBOX-1684X-416 Introduction
        • 2. Demo Simple Operation Guide

          • Simple instructions for using shimeta smart monitoring demo
      • RDK-X5

        • 1. Introduction

          • RDK-X5 Hardware Introduction
        • 2. Quick Start

          • RDK-X5 Quick Start
        • 3. Application Development

          • 3.1 AI Online Model Development

            • AI Online Development - Experiment01
            • AI Online Development - Experiment02
            • AI Online Development - Experiment03
            • AI Online Development - Experiment04
            • AI Online Development - Experiment05
            • AI Online Development - Experiment06
          • 3.2 Large Language Models (Voice)

            • Voice LLM Application - Experiment01
            • Voice LLM Application - Experiment02
            • Voice LLM Application - Experiment03
            • Voice LLM Application - Experiment04
            • Voice LLM Application - Experiment05
            • Voice LLM Application - Experiment06
          • 3.3 40pin-IO Development

            • 40pin IO Development - Experiment01
            • 40pin IO Development - Experiment02
            • 40pin IO Development - Experiment03
            • 40pin IO Development - Experiment04
            • 40pin IO Development - Experiment05
            • 40pin IO Development - Experiment06
            • 40pin IO Development - Experiment07
          • 3.4 USB Module Development

            • USB Module Usage - Experiment01
            • USB Module Usage - Experiment02
          • 3.5 Machine Vision

            • Machine Vision Technology Development - Experiment01
            • Machine Vision Technology Development - Experiment02
            • Machine Vision Technology Development - Experiment03
            • Machine Vision Technology Development - Experiment04
          • 3.6 ROS2 Base Development

            • ROS2 Basic Development - Experiment01
            • ROS2 Basic Development - Experiment02
            • ROS2 Basic Development - Experiment03
            • ROS2 Basic Development - Experiment04
      • RDK-S100

        • 1. Introduction

          • 1.1 About RDK-S100
        • 2. Quick Start

          • 2.1 First Use
        • 3. Application Development

          • 3.1 AI Online Model Development

            • 3.1.1 Volcano Engine Doubao AI
            • 3.1.2 Image Analysis
            • 3.1.3 Multimodal Visual Analysis
            • 3.1.4 Multimodal Image Comparison
            • 3.1.5 Multimodal Document Analysis
            • 3.1.6 Camera AI Vision Analysis
          • 3.2 Large Language Models

            • 3.2.1 Speech Recognition
            • 3.2.2 Voice Conversation
            • 3.2.3 Multimodal Image Analysis
            • 3.2.4 Multimodal Image Comparison
            • 3.2.5 Multimodal Document Analysis
            • 3.2.6 Multimodal Vision Application
          • 3.3 40pin-IO Development

            • 3.3.1 GPIO Output LED Blink
            • 3.3.2 GPIO Input
            • 3.3.3 Key Control LED
            • 3.3.4 PWM Output
            • 3.3.5 Serial Output
            • 3.3.6 I2C Experiment
          • 3.4 USB Module Development

            • 3.4.1 USB Voice Module
            • 3.4.2 Sound Source Localization
          • 3.5 Machine Vision

            • 3.5.1 USB Camera
            • 3.5.2 Image Processing Basics
            • 3.5.3 Object Detection
            • 3.5.4 Image Segmentation
          • 3.6 ROS2 Base Development

            • 3.6.1 Environment Setup
            • 3.6.2 Create and Build Workspace
            • 3.6.3 ROS2 Topic Communication
            • 3.6.4 ROS2 Camera Application
    • Core-Board

      • C-3568BQ

        • 1. Introduction

          • C-3568BQ Introduction
      • C-3588LQ

        • 1. Introduction

          • C-3588LQ Introduction
      • GC-3568JBAF

        • 1. Introduction

          • GC-3568JBAF Introduction
      • C-K1BA

        • 1. Introduction

          • C-K1BA Introduction

05 NAPI Development Practice Demonstration

1. Introduction

In this chapter, I will take GPIO read/write as an example to bring you through a complete analysis of how to create a NAPI project, and explain the project structure and source code in detail. In subsequent usage, we will use this as a basis and will not introduce each peripheral and interface in detail, only providing necessary explanations!

Our application uses the "Native C++" template to implement calling Linux kernel command line interfaces through NAPI (Node-API) to control Rk3568's GPIO on ArkTS.

This chapter is somewhat difficult but very important!

This Chapter Data Path

hap package: /05-Development-Materials/01-OpenHarmony-Development-Materials/Peripheral-Test-APP/HAP/GPIO_TEST.hap

Project source: /05-Development-Materials/01-OpenHarmony-Development-Materials/Peripheral-Test-APP/SRC/GPIO_TEST

2. Target Effect Image to Achieve

Input mode:

GPIO Input Mode Effect

Output mode:

GPIO Output Mode Effect

3. Code Structure Interpretation

This article will interpret the core code. The project code structure generated by the software using Native C++ template is as follows:

Project Code Structure Diagram
  1. napi_init.cpp This is the core file for NAPI development. The main function interface functions are defined here by users. Additionally, NAPI module registration initialization code is also generated under this file. It implements the bridge between JavaScript and C++, providing hardware control functions.

  2. CMakeLists.txt Build configuration file, defines compilation rules and dependencies for C++ module.

  3. Index.ets Main interface page, implements user interface for code control, calls underlying C++ functions through NAPI.

  4. EntryAbility.ets Defines application lifecycle management and main window creation.

  5. EntryBackupAbility.ets Implements application data backup and restore functions.

  6. module.json5 Module configuration file.

  7. oh-package.json5 NAPI module type declaration package configuration file.

4. Index.ets File Calling C/C++ Function Process

4.1 Application Framework

The entire application framework can be simply divided into three parts: C++ side, eTS side, and various toolchains.

  • C++ side: Contains references to various files, C++ or C code, Node_API information linking C++ functions with JavaScript, etc.
  • eTS side: Contains UI, its own methods, calls to reference package methods, etc.
  • Toolchain: Contains series of tools including Cmake packaging tool.
Application Framework Diagram

4.2 Call and Packaging Process

In the process of eTS calling C++ methods, the call and packaging process are as follows:

Call Packaging Flow Chart

5. C++ Side Code Implementation

5.1 Function Code Writing & Interpretation

Below are explanations of several main functions in C++:

First, use 2 functions to implement GPIO (General Purpose Input Output) file operation functions.

void write_gpio_file(const char *filename, const char *value) {
    char path[256];
    snprintf(path, sizeof(path), "%s/%s", GPIO_PATH, filename);
    OH_LOG_Print(LOG_APP, LOG_INFO, GLOBAL_RESMGR, GPIO_TAG,
                       " %{public}s,%{public}s", GPIO_PATH, filename);
    int fd = open(path, O_WRONLY);
    if (fd < 0) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, GLOBAL_RESMGR, GPIO_TAG,
                       " open failed for path:%{public}s, errno:%{public}d, error:%{public}s",
                       path, errno, strerror(errno));
        exit(1);
    }

    if (write(fd, value, strlen(value)) < 0) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, GLOBAL_RESMGR, GPIO_TAG,
                       " open failed for path:%{public}s, errno:%{public}d, error:%{public}s",
                       path, errno, strerror(errno));
        close(fd);
        exit(1);
    }
    close(fd);
}

First, explain the write_gpio_file function. The implemented function is to write a value to the specified GPIO file, used to control GPIO pin state.

Implementation details:

1- Use snprintf to safely construct the complete file path, save the path to path.

2- Open the GPIO file in write-only mode using open. After successfully opening the file, use write to write the target value.

Tip

When errors occur, print logs through OH_LOG_Print for easy troubleshooting.

char* read_gpio_file(const char *filename) {
    char path[256];
    snprintf(path, sizeof(path), "%s/%s", GPIO_PATH, filename);
    OH_LOG_Print(LOG_APP, LOG_INFO, GLOBAL_RESMGR, GPIO_TAG,
                       " reading %{public}s,%{public}s", GPIO_PATH, filename);
    int fd = open(path, O_RDONLY);
    if (fd < 0) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, GLOBAL_RESMGR, GPIO_TAG,
                       " open failed for path:%{public}s, errno:%{public}d, error:%{public}s",
                       path, errno, strerror(errno));
        return nullptr;
    }

    char* buffer = (char*)malloc(32);
    ssize_t bytes_read = read(fd, buffer, 31);
    if (bytes_read < 0) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, GLOBAL_RESMGR, GPIO_TAG,
                       " read failed for path:%{public}s, errno:%{public}d, error:%{public}s",
                       path, errno, strerror(errno));
        close(fd);
        free(buffer);
        return nullptr;
    }

    buffer[bytes_read] = '\0';
    // Remove newline character
    if (bytes_read > 0 && buffer[bytes_read - 1] == '\n') {
        buffer[bytes_read - 1] = '\0';
    }

    close(fd);
    return buffer;
}

Now explain the read_gpio_file function. The implemented function is to read the value from the specified GPIO file, used to obtain GPIO pin state. On success, it returns a dynamically allocated memory pointer. On failure, it returns nullptr.

Implementation details:

1- Use snprintf to safely construct the complete file path, save the path to path.

2- Open the GPIO file in read-only mode using open.

3- Use malloc function to dynamically allocate 32-byte buffer, use read to read the string from the opened file and save to the applied buffer.

4- Return the read value after removing the newline character.

After implementing the above two parts, we can implement file read/write to control the target peripheral. Let's look at specific control function:

// Set GPIO direction
static napi_value SetGpioDirection(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    if (argc < 1) {
        napi_throw_error(env, nullptr, "Expected 1 argument: direction value (in/out)");
        return nullptr;
    }

    size_t str_size;
    napi_get_value_string_utf8(env, args[0], nullptr, 0, &str_size);
    char* direction_value = (char*)malloc(str_size + 1);
    napi_get_value_string_utf8(env, args[0], direction_value, str_size + 1, &str_size);

    write_gpio_file("direction", direction_value);
    free(direction_value);

    napi_value result;
    napi_get_boolean(env, true, &result);
    return result;
}

First is the NAPI function: SetGpioDirection. The function is to set the working direction of GPIO pin (input mode "in" or output mode "out").

First explain the parameters passed to the function:

env NAPI environment context, used for all NAPI calls.

info Callback information, contains parameters passed from JavaScript.

Now analyze the function body:

1- argc represents the number of parameters passed. The args array is used to save the passed parameter values. The function napi_get_cb_info can obtain parameter information passed from JavaScript call, such as the direction "in".

2- First use the function napi_get_value_string_utf8 without providing buffer, only get the length of the passed string by calling the function and save to str_size. After obtaining the target string length, dynamically allocate memory. Use the function napi_get_value_string_utf8 again to extract the target length string value from JavaScript parameters and convert to C string, save to the allocated buffer.

3- Call the previously defined write_gpio_file function to write the direction parameter to the target folder, then free the memory.

4- After successful write, create JavaScript boolean value through napi_get_boolean, save to result and return.

The previous function implements the function of sending commands. Let's analyze a function that reads return value to obtain IO direction:

// Read GPIO direction
static napi_value GetGpioDirection(napi_env env, napi_callback_info info)
{
    char* direction_value = read_gpio_file("direction");
    if (direction_value == nullptr) {
        napi_throw_error(env, nullptr, "Failed to read GPIO direction");
        return nullptr;
    }

    napi_value result;
    napi_create_string_utf8(env, direction_value, NAPI_AUTO_LENGTH, &result);
    free(direction_value);

    return result;
}

The function GetGpioDirection implements the function of reading the current working direction of GPIO pin.

The parameters and return values are the same as other NAPI functions. We only need to care about the function body:

1- First call the previously defined function read_gpio_file to read the direction value of the corresponding IO in the specified folder.

2- Then use the function napi_create_string_utf8 to convert the read C string to JavaScript string, and save the result to result for return (using parameter NAPI_AUTO_LENGTH can automatically calculate string length).

In addition, we also defined functions for setting GPIO level value and reading GPIO level value. The principle is the same as above, so we won't repeat it. Here is the code:

// Set GPIO level value
static napi_value SetGpioValue(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    if (argc < 1) {
        napi_throw_error(env, nullptr, "Expected 1 argument: value (0/1)");
        return nullptr;
    }

    size_t str_size;
    napi_get_value_string_utf8(env, args[0], nullptr, 0, &str_size);
    char* value = (char*)malloc(str_size + 1);
    napi_get_value_string_utf8(env, args[0], value, str_size + 1, &str_size);

    write_gpio_file("value", value);
    free(value);

    napi_value result;
    napi_get_boolean(env, true, &result);
    return result;
};

// Read GPIO level value
static napi_value GetGpioValue(napi_env env, napi_callback_info info)
{
    char* value = read_gpio_file("value");
    if (value == nullptr) {
        napi_throw_error(env, nullptr, "Failed to read GPIO value");
        return nullptr;
    }

    napi_value result;
    napi_create_string_utf8(env, value, NAPI_AUTO_LENGTH, &result);
    free(value);

    return result;
};

5.2 Register Module

Let's go to the end of napi_init.cpp function to write the registered function module.

The module registration writing is fixed. In the Init function, in napi_property_descriptor desc[], we need to supplement the part that associates the implemented function in the project with the exposed interface.

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        { "setGpioDirection", nullptr, SetGpioDirection, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "getGpioDirection", nullptr, GetGpioDirection, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "setGpioValue", nullptr, SetGpioValue, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "getGpioValue", nullptr, GetGpioValue, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "exportGpio", nullptr, ExportGpio, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "unexportGpio", nullptr, UnexportGpio, nullptr, nullptr, nullptr, napi_default, nullptr }
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

napi_module is used to describe module information. Generally, the only thing that needs to be modified is the module name nm_modname, then register it.

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&demoModule);
}

6. Interface Code Implementation

We need to define the external interface methods and simple descriptions in the Index.d.ts file:

export const setGpioDirection: (direction: string) => boolean
export const getGpioDirection: () => string
export const setGpioValue: (value: string) => boolean
export const getGpioValue: () => string
export const exportGpio: () => boolean
export const unexportGpio: () => boolean

The interface names here are consistent with the interface names provided externally when registering the module.

The content after ":" is the parameter that needs to be passed, and after "=>" is the return value, here it returns JavaScript's boolean type.

Next is to configure CMake packaging parameters in CMakeLists.txt:

# the minimum version of CMake.
cmake_minimum_required(VERSION 3.5.0)
project(GPIO)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

if(DEFINED PACKAGE_FIND_FILE)
    include(${PACKAGE_FIND_FILE})
endif()

include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include)

add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC
        libace_napi.z.so
        libhilog_ndk.z.so)

The CMakeLists file basically doesn't need modification. Generally, you just add an extra system library. For example, I just added a libhilog_ndk.z.so library at the end.

7. ETS Side Code Implementation

After all the introduction, it is to call related function functions here.

First, import the NAPI module we defined at the beginning of the file:

import testNapi from 'libentry.so'

Then define the struct components used in this example. @State is a reactive state variable. When data changes, the UI interface will automatically update. We defined IO direction, level value, title and other content used in this project.

@Entry
@Component
struct Index {
  @State title: string = 'ShiMeta Pi';
  @State currentMode: string = 'out'; // Current GPIO direction mode
  @State currentValue: string = '0'; // Current GPIO level value
  @State message: string = 'GPIO154(IO4_D2) Control';
  private intervalId: number = -1; // Timer ID

Our Index.ets part of the code is mainly divided into 2 parts: function modules and UI modules. The purpose of doing secondary encapsulation on function modules is to improve code readability and facilitate later function updates. It's a good programming habit. Below, let's explain in detail the function modules used in the UI.

7.1 Function Module Details

We selected the following functions for detailed explanation:

// Read current GPIO direction
private getCurrentGpioDirection() {
  try {
    const direction = testNapi.getGpioDirection();
    this.currentMode = direction;
    hilog.info(DOMAIN, 'GPIO', `Current GPIO154 Direction: ${direction}`);
  } catch (error) {
    hilog.error(DOMAIN, 'GPIO', `Failed to read GPIO direction: ${error}`);
  }
}

The function getCurrentGpioDirection implements the function of reading IO direction. It calls the declared C++ function getGpioDirection to read the direction value and updates it to the reactive variable representing the current IO working mode.

// Set GPIO level value
private setGpioValue(value: string) {
  try {
    testNapi.setGpioValue(value);
    this.currentValue = value;
    this.message = `GPIO154 level set to ${value === '1' ? 'High' : 'Low'}`;
    hilog.info(DOMAIN, 'GPIO', `GPIO154 level set to: ${value}`);
  } catch (error) {
    hilog.error(DOMAIN, 'GPIO', `Failed to set GPIO level value: ${error}`);
  }
}

The function setGpioValue implements the function of setting IO output level in output mode. It calls the declared C++ function setGpioValue to write the set value to IO, and updates it to the reactive variable representing the current IO output level state.

// Start timer (read level status every 0.1s in input mode)
private startValuePolling() {
  this.stopValuePolling(); // Stop previous timer first
  this.intervalId = setInterval(() => {
    if (this.currentMode === 'in') {
      this.getCurrentGpioValue();
    }
  }, 100);
}

// Stop timer
private stopValuePolling() {
  if (this.intervalId !== -1) {
    clearInterval(this.intervalId);
    this.intervalId = -1;
  }
}

The functions startValuePolling and stopValuePolling are respectively used to start and stop the timer.

The function setInterval is a built-in JavaScript function. The implemented functions are respectively to periodically execute the callback function (here 100ms). After successful creation, it returns a timer ID. ID being -1 means no timer or timer creation failed.

The corresponding function clearInterval is also a built-in JavaScript function. The implemented function is to clear the timer with the specified ID.

// Read current status when component initializes
aboutToAppear() {
  this.getCurrentGpioDirection();
  this.getCurrentGpioValue();
  // If initial mode is input mode, start timer
  if (this.currentMode === 'in') {
    this.startValuePolling();
  }
}

// Cleanup timer when component destroys
aboutToDisappear() {
  this.stopValuePolling();
}

The functions aboutToAppear and aboutToDisappear are respectively the initialization process when APP starts and the de-initialization process when exiting. The functions called are all explained above.

For other function modules, please refer to the source code. I believe after understanding the above functions, the rest will be easy to understand. So I won't repeat them here.

7.2 UI Interface Function Details

build() {
  Row() {
    Column({ space: 40 }) {

      Text(this.title)
        .fontSize(40)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 30 })
        .textAlign(TextAlign.Center)

      Text(this.message)
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })
        .textAlign(TextAlign.Center)

      // Current mode display label
      Text(`Current Mode: ${this.currentMode === 'out' ? 'Output Mode' : 'Input Mode'}`)
        .fontSize(24)
        .fontColor(this.currentMode === 'out' ? '#FF6B35' : '#007DFF')
        .fontWeight(FontWeight.Medium)
        .textAlign(TextAlign.Center)
        .padding(20)
        .backgroundColor(this.currentMode === 'out' ? '#FFF5F0' : '#F0F8FF')
        .borderRadius(10)
        .width('100%')

      // Current level value display label
      Text(`Current Level: ${this.currentValue === '1' ? 'High(1)' : 'Low(0)'}`)
        .fontSize(20)
        .fontColor(this.currentValue === '1' ? '#DC3545' : '#28A745')
        .fontWeight(FontWeight.Medium)
        .textAlign(TextAlign.Center)
        .padding(15)
        .backgroundColor(this.currentValue === '1' ? '#FFF0F0' : '#F0FFF0')
        .borderRadius(8)
        .width('100%')

      // Refresh status button
      Button('Refresh Current Status')
        .width('60%')
        .height(60)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .backgroundColor('#28A745')
        .borderRadius(10)
        .margin({ bottom: 20 })
        .onClick(() => {
          this.getCurrentGpioDirection();
          // Only read level value in input mode
          if (this.currentMode === 'in') {
            this.getCurrentGpioValue();
          }
          this.message = 'Status Refreshed';
        })

      // GPIO direction toggle button
      Button(`Switch to ${this.currentMode === 'out' ? 'Input' : 'Output'} Mode`)
        .width('80%')
        .height(80)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .backgroundColor(this.currentMode === 'out' ? '#007DFF' : '#FF6B35')
        .borderRadius(15)
        .onClick(() => {
          this.toggleGpioDirection();
        })

      // GPIO level value control button group (only displayed in output mode)
      if (this.currentMode === 'out') {
        Row({ space: 20 }) {
          Button('Set Low(0)')
            .width('45%')
            .height(70)
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .backgroundColor('#28A745')
            .borderRadius(12)
            .onClick(() => {
              this.setGpioValue('0');
            })

          Button('Set High(1)')
            .width('45%')
            .height(70)
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .backgroundColor('#DC3545')
            .borderRadius(12)
            .onClick(() => {
              this.setGpioValue('1');
            })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
      }

      // Explanatory text
      Column({ space: 10 }) {
        Text('GPIO154 Control Instructions:')
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .fontColor('#333333')

        Text('• Output Mode: GPIO154 as output pin, can set High/Low level')
          .fontSize(14)
          .fontColor('#666666')
          .textAlign(TextAlign.Start)
          .width('100%')

        Text('• Input Mode: GPIO154 as input pin, can read level status')
          .fontSize(14)
          .fontColor('#666666')
          .textAlign(TextAlign.Start)
          .width('100%')

        Text('• Click toggle button to switch between Input/Output mode')
          .fontSize(14)
          .fontColor('#666666')
          .textAlign(TextAlign.Start)
          .width('100%')

        Text('• In output mode, click button to set High(1) or Low(0)')
          .fontSize(14)
          .fontColor('#666666')
          .textAlign(TextAlign.Start)
          .width('100%')

        Text('• Click refresh button to read current actual status')
          .fontSize(14)
          .fontColor('#666666')
          .textAlign(TextAlign.Start)
          .width('100%')
      }
      .width('100%')
      .padding(20)
      .backgroundColor('#FAFAFA')
      .borderRadius(10)
    }
    .width('100%')
    .padding(30)
    .justifyContent(FlexAlign.Center)
  }
  .height('100%')
  .backgroundColor('#F5F5F5')
}

After studying "ArkTS Getting Started" in the previous chapter, the above code is clear. It's just putting some button and text label components in horizontal and vertical linear layouts, adding some attributes below the components like size, background color, and click events.

The function modules we use in click events are the ones we implemented through NAPI.

8. Code Compilation, Burning & Program Running

After completing the above code writing, after successfully connecting the device, you can use one-click compilation download and run our program in DevEco Studio:

Compile and Burn Program

Wait for compilation to complete. Through the development board screen, you can find it automatically enters the program:

Program Running Interface

Next, we click the "Switch to Input Mode" button.

Unexpectedly, it should flash back to the desktop, because we haven't exported the directory in the board's terminal yet and added executable permissions to the exported directory. First, enter the development board terminal through HDC, and enter commands in order:

echo 154 > /sys/class/gpio/export
chmod 777 /sys/class/gpio/gpio154/*
Terminal Command Execution

At this point, we can use the program normally.

Below is the explanation of the reason:

The command echo 154 > /sys/class/gpio/export means exporting the corresponding IO154 in the gpio directory. At this time, the kernel will create a folder belonging to IO154. Our subsequent operations are on this folder.

The command chmod 777 /sys/class/gpio/gpio154/* is to add permissions to the exported file gpio154. chmod means permission setting. According to file permission representation under Linux kernel, 7=4+2+1: read(4) + write(2) + execute(1) = full permissions. Among them, 777: owner(7) + group user(7) + other users(7) = all users full permissions. The trailing * is a wildcard, representing all files under that directory.

The Linux system default GPIO file permission is 644. The application is not executed as root user, so it doesn't have write permission and cannot execute corresponding commands. However, the OpenHarmony terminal runs as root by default, so it can modify the default file permissions in the terminal.

Tip

Setting permission 777 here is for convenient debugging. In general production environments, it might be set to 664 to prevent program from being tampered with by other users.

Some friends must ask, why not use the command chmod 777 /sys/class/gpio/*, so that we can export in the NAPI program? That's indeed the case. However, after using the command to export, the gpio154 file is not set to 777 permission, because the chmod command only modifies permissions of existing directories. After exporting in the software, we need to go back to the terminal to add 777 permission to the exported gpio154 for the program to run normally. For convenience, we didn't do much. We also provided IO export and unexport interfaces in napi_init.cpp and Index.d.ts programs. You can try adding these interfaces in the Index.ets program yourself.

Friends can use multimeter and DuPont lines to test the program. The author has tested it, but due to limited space and considering there will be a dedicated chapter on GPIO later, I won't demonstrate here.

9. Summary

This chapter's content is long. It introduced how C++ code is associated with JavaScript through toolchain, how eTS files call interfaces provided by so packages, and through code interpretation, led everyone to learn the specific writing and packaging process of C++ code. It is a very important part of this tutorial's NAPI development tutorial. You can review it against our source code. Friends with ShiMetaPi M4-R1 board can also replicate according to the tutorial.

Through the GPIO read/write example, it fully demonstrated the creation and development process of NAPI project, covering key aspects such as C++ side code implementation, interface definition, CMake configuration, ArkTS interface development, laying a solid foundation for subsequent peripheral interface development.

10: Common NAPI Development Issues

Common Issue 1: System Commands Cannot Modify Permissions

When developing some peripherals, you may encounter situations where system commands cannot modify permissions, causing inability to use normally.

Permission Error

Solution:

# 1. Remount root file system as read-write
mount -o remount,rw /

# 2. Change ifconfig permission
chmod 777 /bin/ifconfig
Permission Solution

Common Issue 2: ArkTS Does Not Support Implicit Declaration

ArkTS Type Error

ArkTS requires explicit type declaration (explicit type), cannot use any or unknown type (implicit type).

Common Issue 3: Modify Application Name

Modify Application Name

Common Issue 4: Do I Need to Open Terminal to Set Permissions Every Time Before Development?

In actual projects, we will modify system file permissions through adding system startup scripts, udev rules management permissions, etc. in the system image.

Common Issue 5: APP NAPI Interface Flashes Back When Called (Important!)

Common situation is: clicked a button, its click event called some NAPI interface, but the app flashed back.

Because the software is compiled as 64-bit by default, the development board is 32-bit. If you want to run it on the development board, you need to add a 32-bit compiler, as shown in the figure:

First find the file build-profile.json5 in the entry directory:

build-profile.json5 in entry directory
"buildOption": {
    "externalNativeOptions": {
      "path": "./src/main/cpp/CMakeLists.txt",
      "arguments": "",
      "cppFlags": "",
      "abiFilters": [
        "arm64-v8a",
        "armeabi-v7a"
      ]
    }
  }

However, HarmonyOS does not support 32-bit, only OpenHarmony supports the board's 32-bit processor, so it will report an error.

Find the file with the same name build-profile.json5 in the project root directory:

build-profile.json5 in root directory

Modify the above content to:

"products": [
  {
    "name": "default",
    "signingConfig": "default",
    "compileSdkVersion": 12,
    "compatibleSdkVersion": 12,
    "targetSdkVersion": 12,
    "runtimeOS": "OpenHarmony",
    "buildOption": {
      "strictMode": {
        "caseSensitiveCheck": true,
        "useNormalizedOHMUrl": true
      }
    }
  }
]
Modified Configuration

After modification, reconfigure the project structure:

Modify Project 1Modify Project 2

Finally, if there are issues: When in doubt - restart!

Edit this page on GitHub
Last Updated:
Contributors: ZSL