[NUC980] Remote Control demo (Use C and Paho MQTT Library)

, Jan 20, 2025|
0
12
0

The Remote Control Demo showcases the integration of IoT technologies on the Nuvoton NuMaker-IIoT-NUC980 series development board, leveraging an Embedded Linux OS environment built using Buildroot. This demo uses C and Paho MQTT library. The board communicates with an external MQTT broker via Ethernet and MQTT protocol, to report system status, button states, and control an onboard LED.

Key Features

  • Platform: Nuvoton NuMaker-IIoT-NUC980 or NuMaker-IIoT-NUC980G1 with Embedded Linux
  • Development: Buildroot-based environment tools
  • Communication: Real-time data exchange via MQTT protocol
  • Control Interface: IoT MQTT Panel on Android for user interaction

Prerequisite

  • A NuMaker-IIoT-NUC980 or G1 board

  • A LAN cable.
  • Two MicroUSB cables for NuMaker-IIoT-NUC980 or
    Two USB Type-C cables for NuMaker-IIoT-NUC980G1

  • A Wi-Fi Router with LAN
  • A Thermo 6 Click board for the MAX31875 temperature sensor

  • A PC running Ubuntu. Ubuntu running in VM is fine.
  • Buildroot - https://github.com/OpenNuvoton/buildroot_2024 
  • Terminal Tool - Putty, Realterm, or TeraTerm, etc.
  • MQTT app - IoT MQTT Panel  on Google Play Or other MQTT client app

Demo Environment

  • The NuMaker-IIoT-NUC980 or G1 board connects to test.mosquitto.org using the MQTT protocol.

  • Then it subscribes to the following topics:
    • board/status: The online or offline status of NuMaker board
    • board/leds/1: The status of the LED3 on board
    • board/buttons/1: The pressed or released status of K1 button on board
    • board/temperature/1: The temperature reported from MAX31875 sensor

Software Setup and Firmware Build

  • Buildroot Setup
    • In Ubuntu terminal
      • Clone the buildroot_2024 for NUC980
        $ git clone https://github.com/OpenNuvoton/buildroot_2024

      • Set default configuration for NuMaker-IIoT-NUC980 or G1 board
        $ cd buildroot_2024
        $ make nuvoton_nuc980_iot_defconfig

      • Configure Buildroot to include related packages
        $ make menuconfig

      • Include Paho MQTT library
        Target packages => Libraries => Networking => [*] paho-mqtt-c

      • Then <Save> configuration, <Exit> configuration
      • Configure Linux kernel to support NUC980 keyboard events
        $ make linux-menuconfig
        Device Drivers => Input device support => <*> Event interface
        Device Drivers => Input device support => [*] Keyboards => [*] Nuvoton keys support

      • Then <Save> configuration, <Exit> Linux kernel configuration
      • Build the system image
        $ make
        $ cd ..
        $ vi rcdemo.c
      • Cut and paste to rcdemo.c 
        /*
        
          Remote Control Demo on Nuvoton NUC980
        
          This demo run on the Nuvoton NuMaker NUC980 series board with Embedded Linux,
          Connecting to an MQTT broker to report the board status, button status, and
          temperature reading. Additionally, it receives LED status from the MQTT broker
          and controls the onboard LED based on the received status.
        
          Features:
            1. Reports board and button status to MQTT broker.
            2. Periodically reads temperature from a MAX31875 sensor via I2C and reports it.
            3. Receives LED status from MQTT broker and controls the onboard LED.
            4. Implements thread-based monitoring for button events and temperature readings.
        
          License: MIT License, no warranty.
          Copyright (c) 2025 Nuvoton Technology Corp.
        
        */
        
        /* 
          Command line to build the code
        
          $ source ./buildroot_2024/output/host/environment-setup
          $ $CC rcdemo.c -o rcdemo -Ibuildroot_2024/output/staging/usr/include -Lbuildroot_2024/output/staging/usr/lib -Wl,-Bstatic -lpaho-mqtt3c -Wl,-Bdynamic
        
        */
        
        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
        #include <stdint.h>
        #include <unistd.h>
        #include <pthread.h>
        #include <fcntl.h>             // For open()
        #include <errno.h>
        #include <time.h>
        #include <sys/ioctl.h>
        #include <linux/input.h>       // For key events
        #include <linux/i2c-dev.h>     // For I2C support
        #include <linux/gpio.h>        // For GPIO support
        #include "MQTTClient.h"        // For MQTT support
        
        
        #define ADDRESS     "tcp://test.mosquitto.org:1883"
        #define CLIENTID    "NUC980Client"
        #define QOS         1
        #define TIMEOUT     10000L
        
        // GPIO Definitions for LED
        #define GPIO_CHIP_PATH "/dev/gpiochip0"
        #define GPIO_LINE_LED 40 // GPIO PB8
        
        // Input event file for Key
        #define INPUT_EVENT_PATH "/dev/input/event0"
        #define KEY_CODE 108  // Key code for the button
        
        // I2C for MAX31875
        #define I2C_DEVICE_PATH "/dev/i2c-2"
        #define MAX31875_ADDR 0x48
        #define MAX31875_CONFIG_REG 0x01
        #define MAX31875_TEMP_REG 0x00	
        
        // MQTT Topics
        #define TOPIC_STATUS        "board/status"
        #define TOPIC_LED           "board/leds/1"
        #define TOPIC_BUTTON        "board/buttons/1"
        #define TOPIC_TEMPERATURE   "board/temperature/1"
        
        // GPIO and MQTT variables
        int gpio_fd;
        int led_line_fd;
        MQTTClient client;
        int running = 1; // To control the main loop and threads
        
        // Function to configure GPIO as output
        int gpio_configure_output(int gpio_fd, int line_number)
        {
            struct gpiohandle_request req;
            req.lineoffsets[0] = line_number;
            req.flags = GPIOHANDLE_REQUEST_OUTPUT;
            req.lines = 1;
            req.default_values[0] = 0; // Initially set to LOW
        
            if (ioctl(gpio_fd, GPIO_GET_LINEHANDLE_IOCTL, &req) < 0)
            {
                perror("Failed to configure GPIO as output");
                return -1;
            }
        
            return req.fd; // Return GPIO line file descriptor
        }
        
        // Function to set GPIO output
        int gpio_set_value(int line_fd, int value)
        {
            struct gpiohandle_data data;
            data.values[0] = value;
        
            if (ioctl(line_fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data) < 0)
            {
                perror("Failed to set GPIO value");
                return -1;
            }
        
            return 0;
        }
        
        // Function to initialize GPIO for LED control
        void init_gpio()
        {
            // Open GPIO chip device
            gpio_fd = open(GPIO_CHIP_PATH, O_RDWR);
            if (gpio_fd < 0)
            {
                perror("Failed to open GPIO chip device");
                exit(EXIT_FAILURE);
            }
        
            // Configure GPIO line as output for LED
            led_line_fd = gpio_configure_output(gpio_fd, GPIO_LINE_LED);
            if (led_line_fd < 0)
            {
                close(gpio_fd);
                exit(EXIT_FAILURE);
            }
        }
        
        // Function to clean up GPIO resources
        void cleanup_gpio()
        {
            if (led_line_fd)
                close(led_line_fd);
            
            if (gpio_fd)
                close(gpio_fd);
        }
        
        // Function to initialize the MAX31875 sensor
        int init_temperature_sensor()
        {
            int fd;
            uint8_t config_data[3];
        
            // Open the I2C device
            fd = open(I2C_DEVICE_PATH, O_RDWR);
            if (fd < 0)
            {
                perror("Failed to open I2C device");
                return -1;
            }
        
            // Set the I2C slave address
            if (ioctl(fd, I2C_SLAVE, MAX31875_ADDR) < 0)
            {
                perror("Failed to set I2C slave address");
                close(fd);
                return -1;
            }
        
            // Prepare the configuration data
            config_data[0] = MAX31875_CONFIG_REG;   // Point to the configuration register
            config_data[1] = 0x00;                  // High byte of configuration value
            config_data[2] = 0x60;                  // Low byte of configuration value
        
            // Write the configuration data to the sensor
            if (write(fd, config_data, sizeof(config_data)) != sizeof(config_data))
            {
                perror("Failed to write configuration to MAX31875");
                close(fd);
                return -1;
            }
        
            // Sleep for 100ms to allow the sensor to initialize
            usleep(100000);
        
            // Close the I2C device
            close(fd);
        
            printf("MAX31875 sensor initialized successfully\n");
            return 0;
        }
        
        // Function to read temperature from MAX31875
        float read_temperature()
        {
            int fd;
            uint8_t buf[2];                 // Buffer to store data
            float temperature = -999.0;     // Default invalid temperature value
        
            // Open the I2C device
            fd = open(I2C_DEVICE_PATH, O_RDWR);
            if (fd < 0)
            {
                perror("Failed to open I2C device");
                return temperature;
            }
        
            // Set the I2C slave address
            if (ioctl(fd, I2C_SLAVE, MAX31875_ADDR) < 0)
            {
                perror("Failed to set I2C address");
                close(fd);
                return temperature;
            }
        
            buf[0] = MAX31875_TEMP_REG;
            buf[1] = 0;
        	
            // Write to sensor command register
            if (write(fd, buf, 1) != 1)
            {
                perror("Failed to write configuration to MAX31875");
                close(fd);
                return temperature;
            }
        
            // Read 2 bytes from the MAX31875
            if (read(fd, buf, 2) != 2)
            {
                perror("Failed to read temperature data");
                close(fd);
                return temperature;
            }
        
            // Close the I2C device
            close(fd);
        
            // Extract the temperature data from the second and third bytes
            int temp_raw = (buf[0] << 8) | buf[1];
        
            // Shift the raw temperature data to account for the sensor's 12-bit resolution
            temp_raw >>= 4;
        
            // Convert raw data to temperature in Celsius (each LSB represents 0.0625°C)
            temperature = temp_raw * 0.0625f;
        
            return temperature;
        }
        
        // Thread function for MQTT
        void *mqtt_client_thread(void *arg)
        {
            char *topicName;
            int topicLen;
            MQTTClient_message *message; 
            int rc;
            
            printf("Waiting for MQTT messages...\n");
            while(running) {
            
                rc = MQTTClient_receive(client, &topicName, &topicLen, &message, 1000);
                
                if (rc == MQTTCLIENT_SUCCESS && message != NULL)
                {
                    printf("Message received on topic: %s\n", topicName);
                    printf("Message payload: %.*s\n", (int)message->payloadlen, (char *)message->payload);
        
                    if (strcmp(topicName, TOPIC_LED) == 0)
                    {
                        int led_state = (strncmp((char *)message->payload, "on", 2) == 0) ? 1 : 0;
                        if (gpio_set_value(led_line_fd, 1-led_state) < 0)
                        {
                            perror("Failed to set GPIO value for LED");
                        }
                        else
                        {
                            printf("LED set to: %s\n", led_state ? "ON" : "OFF");
                        }
                    }
        
                    MQTTClient_freeMessage(&message);
                    MQTTClient_free(topicName);
                }
                else if (rc == MQTTCLIENT_SUCCESS && message == NULL)
                {
                    // No message received within timeout.
                }
                else
                {
                    printf("Error receiving message: %d\n", rc);
                }
            }
            
            return NULL;
        }
        
        // Thread function to monitor the key state
        void *key_monitor_thread(void *arg)
        {
            int fd = open(INPUT_EVENT_PATH, O_RDONLY);
            if (fd < 0)
            {
                perror("Failed to open input event device");
                pthread_exit(NULL);
            }
        
            struct input_event ev;
            while (running)
            {
                if (read(fd, &ev, sizeof(ev)) == sizeof(ev))
                {
                    if (ev.type == EV_KEY && ev.code == KEY_CODE)
                    {
                        if (ev.value == 1)
                        {
                            printf("Key pressed\n");
                            MQTTClient_publish(client, TOPIC_BUTTON, strlen("pressed"), "pressed", QOS, 0, NULL);
                        }
                        else if (ev.value == 0)
                        {
                            printf("Key released\n");
                            MQTTClient_publish(client, TOPIC_BUTTON, strlen("released"), "released", QOS, 0, NULL);
                        }
                    }
                }
                else
                {
                    perror("Failed to read input event");
                    break;
                }
            }
        
            close(fd);
            pthread_exit(NULL);
        }
        
        // Thread function to periodically report board status
        void *board_status_thread(void *arg)
        {
            while (running)
            {
                const char *status = "online";
                MQTTClient_publish(client, TOPIC_STATUS, strlen(status), status, QOS, 0, NULL);
                printf("Board status reported: %s\n", status);
                sleep(60); // Report status every 60 seconds
            }
        
            pthread_exit(NULL);
        }
        
        // Thread function to periodically read and publish temperature
        void *temperature_thread(void *arg)
        {
            while (running)
            {
                float temp = read_temperature();
                if (temp != -999.0)
                {
                    char temp_str[16];
                    snprintf(temp_str, sizeof(temp_str), "%.2f", temp);
                    MQTTClient_publish(client, TOPIC_TEMPERATURE, strlen(temp_str), temp_str, QOS, 0, NULL);
                    printf("Temperature reported: %s°C\n", temp_str);
                }
                else
                {
                    perror("Failed to read temperature sensor");
                }
        
                sleep(5); // Report temperature every 5 seconds
            }
            pthread_exit(NULL);
        }
        
        int main(int argc, char *argv[])
        {
            MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
            pthread_t key_thread, status_thread, temp_thread, mqtt_thread;
            int rc;
        
            printf("\nRemote Control Demo\n");
            perror("Error Output Test");
        	
            // Initialize GPIO for LED
            printf("Init GPIO...\n");
            init_gpio();
        
            // Initialize temperature sensor
            printf("Init temperature sensor...\n");
            init_temperature_sensor();
        	
            // Initialize MQTT client
            printf("Init MQTT Client...\n");
            MQTTClient_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL);
            conn_opts.keepAliveInterval = 20;
            conn_opts.cleansession = 1;
        
            if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
            {
                printf("Failed to connect to MQTT broker, return code %d\n", rc);
                cleanup_gpio();
                exit(EXIT_FAILURE);
            }
            printf("Connected to MQTT broker.\n");
        
            if ((rc = MQTTClient_subscribe(client, TOPIC_LED, QOS)) != MQTTCLIENT_SUCCESS)
            {
                printf("Failed to subscribe topic %s, return code %d\n", TOPIC_LED, rc); 
            }
            else
            {
                printf("Subscribed to topic: %s\n", TOPIC_LED);
            }
            
            // Publish initial board status
            const char *status = "online";
            MQTTClient_publish(client, TOPIC_STATUS, strlen(status), status, QOS, 0, NULL);
        
            // Start threads
            printf("Starting threads...\n");
            pthread_create(&key_thread, NULL, key_monitor_thread, NULL);
            pthread_create(&status_thread, NULL, board_status_thread, NULL);
            pthread_create(&temp_thread, NULL, temperature_thread, NULL);
            pthread_create(&mqtt_thread, NULL, mqtt_client_thread, NULL);
            
            // Main loop to keep the MQTT client running
            printf("Listening for MQTT messages, key events, and reporting board status & temperature...\n");
            
            while (running)
            {
                sleep(60);
            }
        
            // Clean up
            running = 0;
            pthread_cancel(key_thread);
            pthread_join(key_thread, NULL);
            pthread_cancel(status_thread);
            pthread_join(status_thread, NULL);
            pthread_cancel(temp_thread);
            pthread_join(temp_thread, NULL);
            MQTTClient_disconnect(client, 10000);
            MQTTClient_destroy(&client);
            cleanup_gpio();
        
            return 0;
        }
        

      • Setup cross-compiler environment
        $ source buildroot_2024/output/host/environment-setup

      • Compile the demo source code – rcdemo.c
        $ $CC rcdemo.c -o rcdemo -Ibuildroot_2024/output/staging/usr/include -Lbuildroot_2024/output/staging/usr/lib -Wl,-Bstatic -lpaho-mqtt3c -Wl,-Bdynamic

      • Copy execution file to root account directory
        $ cp rcdemo buildroot_2024/output/target/root

      • Make the image again to include rcdemo execution file
        $ cd buildroot_2024
        $ make
  • After make completed, get files from the following subdirectory in Buildroot
    • ./output/images/u-boot.bin
    • ./output/build/uboot-custom/spl/u-boot-spl.bin
    • ./output/images/nuc980-iot-v1.0.dtb
    • ./output/images/uImage
  • Use a plain text editor to create the env.txt file with the following content
    baudrate=115200
    bootdelay=1
    stderr=serial
    stdin=serial
    stdout=serial
    setspi=sf probe 0 30000000
    loadkernel=sf read 0x7fc0 0x200000 0x1000000
    loaddtb=sf read 0x1800000 0x1200000 0x20000
    bootcmd=run setspi;run loadkernel;run loaddtb;bootm 0x7fc0 - 0x1800000

Burn Firmware to Board

  • Run any terminal tool on the PC/Laptop, such as TeraTerm, Putty, etc. to open a terminal for NUC980 VCOM, named “USB Serial Device”.
  • Select baud rate to 115200. Refer right diagram for other parameters.

  • Set NuMaker-IIoT-NUC980 or G1 board to boot from USB
    • Switch both No.1 and No.2 of SW1 to ON.

    • Press Reset (K3) button on board to boot from USB
    • Use the NuWriter to burn files with the following types and addresses.
      • Clone or Download from https://github.com/OpenNuvoton/NUC980_NuWriter
        • NuWriter Tool is in the ".\NuWriter\NuWriter\Release" directory
        • NuWriter Windows Driver is in the ".\NuWriter\Driver" directory
        • NuWriter Document is the PDF file in the ".\" directory
      • DDR parameter: NUC980DK61Y.ini or NUC980DK63YC.ini depends the NUC980 on board
      • Choose Type: SPI NAND
        FilesTypeAddress
        u-boot-spl.binLoader0x200
        u-boot.binData0x100000
        uImageData0x200000
        nuc980-iot-v1.0-dtbData0x1200000
        env.txtEnvironment0x80000

Boot the Board and Run the Demo

  • Set NuMaker-IIoT-NUC980G2 board to boot from SPI Flash
    • Switch both No.1 and No.2 of SW1 to OFF.
    • Press Reset (K3) button to reboot the board from SPI Flash.
  • Use “root” as account to log in.

  • Enable Ethernet and obtain IP address from DHCP server

  • Run the rcdemo

  • NUC980 board connects to MQTT broker, subscribe and publish status to MQTT broker.

Configure MQTT app

Please refer the Configure MQTT app session of the article (https://forum.nuvoton.com/zh/application-platform/iot-platform/13053)

Files in the demo

The attached ZIP file contains precompiled firmware images, an env.txt, and C source code. These are provided for your convenience and easy use.
NUC980_Remote_Control_Demo_uses_C.zipNUC980_Remote_Control_Demo_uses_C.zip

Conclusion

  • This demonstration highlights the use of the Nuvoton NuMaker-IIoT-NUC980 series board running Embedded Linux, enabling remote control through MQTT protocol and a mobile IoT control panel app.
  • Simply use C and Paho MQTT library for coding, the demo effectively shows how to control on-board peripherals (like LEDs and buttons) and monitor environmental data (such as temperature) remotely.
  • This project demonstrates the seamless integration of hardware, software, and cloud services, offering a practical solution for IoT-based remote control applications.