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
Prerequisite
Demo Environment
Software Setup and Firmware Build
$ git clone https://github.com/OpenNuvoton/buildroot_2024
$ cd buildroot_2024 $ make nuvoton_nuc980_iot_defconfig
$ make menuconfig
Target packages => Libraries => Networking => [*] paho-mqtt-c
$ make linux-menuconfig
Device Drivers => Input device support => <*> Event interface
Device Drivers => Input device support => [*] Keyboards => [*] Nuvoton keys support
$ make
$ cd .. $ vi 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; }
$ 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
$ cp rcdemo buildroot_2024/output/target/root
$ cd buildroot_2024 $ make
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
Files | Type | Address |
u-boot-spl.bin | Loader | 0x200 |
u-boot.bin | Data | 0x100000 |
uImage | Data | 0x200000 |
nuc980-iot-v1.0-dtb | Data | 0x1200000 |
env.txt | Environment | 0x80000 |
Boot the Board and Run the Demo
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