Week 14
Interface and Application Programming
// MAIN OBJECTIVE \\
Design, build and connect wired or wireless node(s) with network or bus addresses and a local input and/or output device(s).
Project Description
For this week, my goal was to bridge the gap between hardware and software by creating an interactive graphical user interface. I designed and developed a web-based dashboard that connects directly to a XIAO ESP32-C6 microcontroller via a wired USB-C connection. The interface acts as a control center where the user can toggle an onboard LED, control its brightness with a interactive tool and monitor real-time sensor data coming from a BMI160 gyroscope.
USB Communication & Web Integration
To achieve this communication without needing intermediary software or local servers, I utilized the Web Serial API. This is a powerful feature in modern browsers that allows websites like Google Chrome, Edge or Opera to request direct read and write access to serial devices connected to the computer.
Note: Safari and FireFox doesn't work with this kind of technology because they don't work with Chromium.
How it works:
The XIAO ESP32-C6 utilizes its native USB Serial/JTAG controller to stream data back and forth. When the user interacts with the webpage and clicks the connection button, the browser triggers a native security prompt asking the user to select the correct COM port, it updates correctly when you connect a new device. Once access is granted, the JavaScript logic on the page establishes a continuous data stream.Controling Outputs:
When the user clicks the Toggle LED button or moves the brightness slider on the web interface, the JavaScript code sends specific string commands likeLED_TOGGLE or PWM:128 over the serial connection. The C code on the ESP32 parses these strings and executes the corresponding hardware actions using PWM functions.
Monitoring Inputs:
Simultaneously, the microcontroller continuously reads data from the BMI160 sensor via I2C. It calculates the Pitch and Roll angles and sends them back over the USB serial connection. The web page constantly listens to this stream, parses the incoming text, and updates the UI dynamically to show the real-time physical orientation of the board.Microcontroller & Interface Code
The firmware running on the XIAO ESP32-C6 was written in C using the ESP-IDF framework. It utilizes FreeRTOS tasks to separate the sensor reading loop from the serial command parsing loop, ensuring that LED control remains highly responsive even while processing continuous I2C data.
Coding in C
Here is the code that I made for the Xiao, ensuring the USB Jtag serial connection works properly and in real time:
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c.h"
#include "driver/ledc.h"
#include "driver/gpio.h"
#include "driver/usb_serial_jtag.h" // usb c library
#include "esp_timer.h"
#include "esp_err.h"
#define I2C_MASTER_SCL_IO 23
#define I2C_MASTER_SDA_IO 22
#define I2C_MASTER_NUM 0
#define I2C_MASTER_FREQ_HZ 100000
#define I2C_MASTER_TX_BUF_DISABLE 0
#define I2C_MASTER_RX_BUF_DISABLE 0
#define SENSOR_ADDR 0x68
#define PI 3.14159265358979323846
// LED Configuration
#define LED_PIN 17
#define LEDC_TIMER LEDC_TIMER_0
#define LEDC_MODE LEDC_LOW_SPEED_MODE
#define LEDC_OUTPUT_IO LED_PIN
#define LEDC_CHANNEL LEDC_CHANNEL_0
#define LEDC_DUTY_RES LEDC_TIMER_8_BIT
#define LEDC_FREQUENCY 5000
volatile bool monitor_active = false;
volatile bool led_state = false;
static esp_err_t i2c_master_init(void) {
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
i2c_param_config(I2C_MASTER_NUM, &conf);
return i2c_driver_install(I2C_MASTER_NUM, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
}
static void ledc_init(void) {
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_MODE,
.timer_num = LEDC_TIMER,
.duty_resolution = LEDC_DUTY_RES,
.freq_hz = LEDC_FREQUENCY,
.clk_cfg = LEDC_AUTO_CLK
};
ledc_timer_config(&ledc_timer);
ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_MODE,
.channel = LEDC_CHANNEL,
.timer_sel = LEDC_TIMER,
.intr_type = LEDC_INTR_DISABLE,
.gpio_num = LEDC_OUTPUT_IO,
.duty = 0,
.hpoint = 0
};
ledc_channel_config(&ledc_channel);
}
void sensor_task(void *arg) {
ESP_ERROR_CHECK(i2c_master_init());
uint8_t cmd_reg = 0x7E;
uint8_t cmd_accel = 0x11;
uint8_t write_accel[2] = {cmd_reg, cmd_accel};
i2c_master_write_to_device(I2C_MASTER_NUM, SENSOR_ADDR, write_accel, 2, 1000 / portTICK_PERIOD_MS);
vTaskDelay(50 / portTICK_PERIOD_MS);
uint8_t cmd_gyro = 0x15;
uint8_t write_gyro[2] = {cmd_reg, cmd_gyro};
i2c_master_write_to_device(I2C_MASTER_NUM, SENSOR_ADDR, write_gyro, 2, 1000 / portTICK_PERIOD_MS);
vTaskDelay(100 / portTICK_PERIOD_MS);
uint8_t reg = 0x0C;
uint8_t memoria[12];
float angulo_roll_filtrado = 0.0f;
float angulo_pitch_filtrado = 0.0f;
uint64_t tiempo_anterior = esp_timer_get_time();
while (1) {
esp_err_t err = i2c_master_write_read_device(I2C_MASTER_NUM, SENSOR_ADDR, ®, 1, memoria, 12, 100 / portTICK_PERIOD_MS);
if (err == ESP_OK) {
uint64_t tiempo_actual = esp_timer_get_time();
float dt = (tiempo_actual - tiempo_anterior) / 1000000.0f;
tiempo_anterior = tiempo_actual;
int16_t gyro_x_raw = (memoria[1] << 8) | memoria[0];
int16_t gyro_y_raw = (memoria[3] << 8) | memoria[2];
int16_t accel_x_raw = (memoria[7] << 8) | memoria[6];
int16_t accel_y_raw = (memoria[9] << 8) | memoria[8];
int16_t accel_z_raw = (memoria[11] << 8) | memoria[10];
float gyro_x_rate = gyro_x_raw / 16.4f;
float gyro_y_rate = gyro_y_raw / 16.4f;
float g_x = accel_x_raw / 16384.0f;
float g_y = accel_y_raw / 16384.0f;
float g_z = accel_z_raw / 16384.0f;
float accel_roll = atan2(g_y, g_z) * (180.0 / PI);
float accel_pitch = atan2(-g_x, sqrt(g_y * g_y + g_z * g_z)) * (180.0 / PI);
angulo_roll_filtrado = 0.98f * (angulo_roll_filtrado + gyro_x_rate * dt) + 0.02f * accel_roll;
angulo_pitch_filtrado = 0.98f * (angulo_pitch_filtrado + gyro_y_rate * dt) + 0.02f * accel_pitch;
// USB send function
if (monitor_active) {
char buffer_out[64];
int len = snprintf(buffer_out, sizeof(buffer_out), "Roll: %.2f | Pitch: %.2f\n", angulo_roll_filtrado, angulo_pitch_filtrado);
usb_serial_jtag_write_bytes((const void*)buffer_out, len, 20 / portTICK_PERIOD_MS);
}
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
void serial_task(void *arg) {
char buffer[128];
int pos = 0;
uint8_t rx_buf[64];
while (1) {
// reading webpage
int rx_len = usb_serial_jtag_read_bytes(rx_buf, sizeof(rx_buf), 20 / portTICK_PERIOD_MS);
if (rx_len > 0) {
for (int i = 0; i < rx_len; i++) {
char c = (char)rx_buf[i];
if (c == '\n' || c == '\r') {
if (pos > 0) {
buffer[pos] = '\0';
if (strcmp(buffer, "LED_TOGGLE") == 0) {
led_state = !led_state;
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, led_state ? 128 : 0);
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
}
else if (strncmp(buffer, "PWM:", 4) == 0) {
int duty = atoi(buffer + 4);
if (duty >= 0 && duty <= 255) {
led_state = (duty > 0);
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty);
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
}
}
else if (strcmp(buffer, "MONITOR_START") == 0) {
monitor_active = true;
}
else if (strcmp(buffer, "MONITOR_STOP") == 0) {
monitor_active = false;
}
pos = 0;
}
} else {
if (pos < sizeof(buffer) - 1) {
buffer[pos++] = c;
}
}
}
}
}
}
void app_main(void) {
// Install the native driver
usb_serial_jtag_driver_config_t usb_config = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT();
usb_serial_jtag_driver_install(&usb_config);
ledc_init();
xTaskCreate(sensor_task, "sensor_task", 4096, NULL, 5, NULL);
xTaskCreate(serial_task, "serial_task", 4096, NULL, 5, NULL);
}
Results and Hero Shot
Thanks to my prior knowledge and experience in web page creation and design, making the visual extension for this assignment was a very smooth and satisfying process. I was able to apply my UI/UX skills to structure a clean, modern, and highly responsive control panel that perfectly complements the robust C firmware running on the hardware.
Hero Shot: Controlling the LED and reading sensor data directly from the web interface.
Interactive Dashboard
You can test and view the full web communications interface I built for this week right here:
→ Access the Communications Page ←Files
Here you can download all the source codes (C firmware and HTML/JS Web Application) of this week's project: