Novaglide Source Code
January 08, 2026
main source code
ESP-IDF_NovaGlide.c
#include "esp_log_level.h"
#include "espnow_sys.h"
#include "esp_log.h"
#include "soc/gpio_num.h"
#include "system_init.h"
#include "scheduler.h"
#include "control_task.h"
// Subsystems
#include "motors.h"
#include "adc.h"
#include "temp_sensor.h"
#include "ina219_sensor.h"
#include "ultrasonic_sensor.h"
#include "wifi_sys.h"
#include "espnow_sys.h"
#include "mqtt_sys.h"
#include "ui.h"
#include "dashboard.h"
#include "i2c_bus.h"
// Telemetry bridge context
typedef struct {
temp_sensor_system_t *temp;
ina219_system_t *ina;
motor_system_t *motors;
mqtt_system_t *mqtt;
ultrasonic_system_t *ultrasonic;
} telemetry_context_t;
// Task to bridge sensor data to MQTT
static void telemetry_bridge_task(void *arg) {
telemetry_context_t *ctx = (telemetry_context_t *)arg;
while (1) {
// Update MQTT with latest sensor readings
mqtt_update_temp(ctx->mqtt, ctx->temp->temperature);
mqtt_update_battery(ctx->mqtt, ctx->ina->bus_voltage);
mqtt_update_current(ctx->mqtt, ctx->ina->current * 1000.0f);
mqtt_update_power(ctx->mqtt, ctx->ina->power);
mqtt_update_pwm(ctx->mqtt, ctx->motors->left_pwm, ctx->motors->right_pwm);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// Display task to show current joystick values
static void display_joystick_task(void *arg) {
espnow_system_t *espnow = (espnow_system_t *)arg;
while (1) {
ESP_LOGI("DISPLAY", "╔════════════════════════════════════╗");
ESP_LOGI("DISPLAY", "║ CURRENT JOYSTICK VALUES ║");
ESP_LOGI("DISPLAY", "║ X-axis: %-8d ║", espnow->last_data.x_axis);
ESP_LOGI("DISPLAY", "║ Y-axis: %-8d ║", espnow->last_data.y_axis);
ESP_LOGI("DISPLAY", "╚════════════════════════════════════╝");
vTaskDelay(pdMS_TO_TICKS(2000)); // Display every 2 seconds
}
}
void app_main(void)
{
// System-level initialization
system_init();
// Set log levels to WARNING or ERROR only
esp_log_level_set("*", ESP_LOG_ERROR);
esp_log_level_set("DASHBOARD", ESP_LOG_INFO); // Allow dashboard
esp_log_level_set("i2c.master", ESP_LOG_NONE); // Suppress I2C NACK errors (normal when ultrasonic is too close)
// Subsystem instances
static motor_system_t motors;
static adc_system_t adc;
static temp_sensor_system_t temp;
static ina219_system_t ina;
static ultrasonic_system_t ultra;
static mqtt_system_t mqtt;
static espnow_system_t espnow;
static ui_system_t ui;
// Initialize WiFi first (needed for ESP-NOW and MQTT)
wifi_system_init();
// Initialize I2C bus FIRST
ESP_ERROR_CHECK(i2c_bus_init());
i2c_bus_scan();
// Initialize all subsystems
motor_system_init(&motors);
adc_system_init(&adc);
temp_sensor_system_init(&temp);
ina219_system_init(&ina);
ultrasonic_system_init(&ultra);
espnow_system_init(&espnow);
mqtt_system_init(&mqtt);
ui_system_init(&ui);
// Start display task (optional - uncomment if needed)
// xTaskCreate(display_joystick_task, "display", 2048, &espnow, 4, NULL);
// Start control task (joystick -> motors)
control_task_start(&motors, &espnow);
// Start dashboard display
static dashboard_context_t dash_ctx = {
.motors = &motors,
.espnow = &espnow,
.temp = &temp,
.ina = &ina,
.ultrasonic = &ultra
};
dashboard_task_start(&dash_ctx);
// Create data bridge task for MQTT telemetry
static telemetry_context_t telem_ctx = {
.temp = &temp,
.ina = &ina,
.motors = &motors,
.mqtt = &mqtt,
.ultrasonic = &ultra
};
xTaskCreate(telemetry_bridge_task, "telemetry", 4096, &telem_ctx, 5, NULL);
// Scheduler wiring
static scheduler_t sched = {
.motors = &motors,
.adc = &adc,
.temp = &temp,
.ina = &ina,
.ultra = &ultra,
.mqtt = &mqtt,
.espnow = &espnow,
.ui = &ui
};
scheduler_init(&sched);
scheduler_start(&sched);
}
control_task.c
#include "control_task.h"
#include "joystick.h"
#include "esp_log.h"
static const char *TAG = "CONTROL";
typedef struct {
motor_system_t *motors;
espnow_system_t *espnow;
} control_context_t;
static joystick_hal_t js;
static void control_task(void *arg) {
control_context_t *ctx = (control_context_t *)arg;
int pwm_left = 0;
int pwm_right = 0;
ESP_LOGI(TAG, "Control task started");
// Initialize joystick HAL
joystick_hal_init(&js);
while (1) {
// 1. Read raw joystick values from ESP-NOW
int32_t rc_x = ctx->espnow->last_data.x_axis;
int32_t rc_y = ctx->espnow->last_data.y_axis;
// 2. Update joystick HAL (auto-calibration + normalization)
js.update(&js, rc_x, rc_y);
// 3. Mix normalized joystick values into motor PWM
joystick_mix(js.norm_y, js.norm_x, &pwm_left, &pwm_right);
// 4. Apply PWM to motors
update_motors_pwm(ctx->motors, pwm_left, pwm_right);
// 5. Debug output
ESP_LOGI(TAG,
"RC raw=(%ld,%ld) norm=(%.2f,%.2f) PWM(L,R)=(%d,%d)",
(long)rc_x, (long)rc_y,
js.norm_x, js.norm_y,
pwm_left, pwm_right);
vTaskDelay(pdMS_TO_TICKS(50)); // 20Hz control loop
}
}
void control_task_start(motor_system_t *motors, espnow_system_t *espnow) {
static control_context_t ctx;
ctx.motors = motors;
ctx.espnow = espnow;
xTaskCreate(control_task, "control", 4096, &ctx, 15, NULL);
ESP_LOGI(TAG, "Control task created");
}
control_task.h
#ifndef CONTROL_TASK_H
#define CONTROL_TASK_H
#include "motors.h"
#include "espnow_sys.h"
void control_task_start(motor_system_t *motors, espnow_system_t *espnow);
#endif
dashboard.c
// dashboard.c
#include "dashboard.h"
#include "ultrasonic_sensor.h"
#include <stdio.h>
#include "esp_log.h"
// ANSI escape codes for terminal control
#define CLEAR_SCREEN "\033[2J"
#define CURSOR_HOME "\033[H"
#define CURSOR_HIDE "\033[?25l"
#define CURSOR_SHOW "\033[?25h"
#define COLOR_RESET "\033[0m"
#define COLOR_GREEN "\033[32m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_CYAN "\033[36m"
#define COLOR_RED "\033[31m"
#define COLOR_BLUE "\033[34m"
#define COLOR_MAGENTA "\033[35m"
#define BOLD "\033[1m"
static void draw_box(const char *title) {
printf("╔════════════════════════════════════════════════════════════╗\n");
printf("║ " COLOR_CYAN BOLD "%-58s" COLOR_RESET " ║\n", title);
printf("╠════════════════════════════════════════════════════════════╣\n");
}
static void draw_line(const char *label, const char *value, const char *color) {
printf("║ " BOLD "%-20s" COLOR_RESET " : %s%-33s" COLOR_RESET " ║\n", label, color, value);
}
static void draw_separator() {
printf("╠════════════════════════════════════════════════════════════╣\n");
}
static void draw_bottom() {
printf("╚════════════════════════════════════════════════════════════╝\n");
}
static void dashboard_task(void *arg) {
dashboard_context_t *ctx = (dashboard_context_t *)arg;
char buffer[50];
// Hide cursor for cleaner display
printf(CURSOR_HIDE);
// Print help text ONCE before the loop
printf("\n" COLOR_YELLOW "Dashboard Mode: Press Ctrl+] to exit monitor" COLOR_RESET "\n");
vTaskDelay(pdMS_TO_TICKS(2000)); // Give user time to read
while (1) {
// Clear screen and move cursor to home
printf(CLEAR_SCREEN CURSOR_HOME);
// ========== HEADER ==========
draw_box("ESP32-C3 ROBOT CONTROL DASHBOARD");
// ========== ESP-NOW SECTION ==========
snprintf(buffer, sizeof(buffer), "%d", ctx->espnow->last_data.x_axis);
draw_line("Joystick X", buffer, COLOR_GREEN);
snprintf(buffer, sizeof(buffer), "%d", ctx->espnow->last_data.y_axis);
draw_line("Joystick Y", buffer, COLOR_GREEN);
draw_separator();
// ========== MOTORS SECTION ==========
snprintf(buffer, sizeof(buffer), "%d", ctx->motors->left_pwm);
draw_line("PWM Left", buffer, COLOR_YELLOW);
snprintf(buffer, sizeof(buffer), "%d", ctx->motors->right_pwm);
draw_line("PWM Right", buffer, COLOR_YELLOW);
draw_separator();
snprintf(buffer, sizeof(buffer), "%d", ctx->motors->motor1_rpm_pcm);
draw_line("Motor 1 (L-Fwd)", buffer,
ctx->motors->motor1_rpm_pcm > 0 ? COLOR_GREEN : COLOR_RESET);
snprintf(buffer, sizeof(buffer), "%d", ctx->motors->motor2_rpm_pcm);
draw_line("Motor 2 (R-Fwd)", buffer,
ctx->motors->motor2_rpm_pcm > 0 ? COLOR_GREEN : COLOR_RESET);
snprintf(buffer, sizeof(buffer), "%d", ctx->motors->motor3_rpm_pcm);
draw_line("Motor 3 (L-Rev)", buffer,
ctx->motors->motor3_rpm_pcm > 0 ? COLOR_RED : COLOR_RESET);
snprintf(buffer, sizeof(buffer), "%d", ctx->motors->motor4_rpm_pcm);
draw_line("Motor 4 (R-Rev)", buffer,
ctx->motors->motor4_rpm_pcm > 0 ? COLOR_RED : COLOR_RESET);
draw_separator();
// ========== SENSORS SECTION ==========
snprintf(buffer, sizeof(buffer), "%.2f °C", ctx->temp->temperature);
draw_line("Temperature", buffer, COLOR_CYAN);
snprintf(buffer, sizeof(buffer), "%.2f V", ctx->ina->bus_voltage);
draw_line("Battery Voltage", buffer,
ctx->ina->bus_voltage > 7.0 ? COLOR_GREEN : COLOR_RED);
snprintf(buffer, sizeof(buffer), "%.2f mA", ctx->ina->current * 1000.0f);
draw_line("Current", buffer, COLOR_MAGENTA);
snprintf(buffer, sizeof(buffer), "%.2f mW", ctx->ina->power * 1000.0f);
draw_line("Power", buffer, COLOR_BLUE);
draw_separator();
snprintf(buffer, sizeof(buffer), "%.2f cm", ctx->ultrasonic->distance_cm / 10.0f);
draw_line("Distance", buffer, COLOR_BLUE);
draw_bottom();
// Status bar at bottom
//printf("\n" COLOR_YELLOW "Press Ctrl+] to exit monitor" COLOR_RESET "\n");
// Update every 200ms for smooth display
vTaskDelay(pdMS_TO_TICKS(1000));
}
// Show cursor on exit (won't actually run due to while loop)
printf(CURSOR_SHOW);
}
void dashboard_task_start(dashboard_context_t *ctx) {
xTaskCreate(dashboard_task, "dashboard", 4096, ctx, 6, NULL);
ESP_LOGI("DASHBOARD", "Dashboard display started");
}
dashboard.h
// dashboard.h
#ifndef DASHBOARD_H
#define DASHBOARD_H
#include "freertos/FreeRTOS.h"
#include "motors.h"
#include "espnow_sys.h"
#include "temp_sensor.h"
#include "ina219_sensor.h"
#include "ultrasonic_sensor.h"
typedef struct {
motor_system_t *motors;
espnow_system_t *espnow;
temp_sensor_system_t *temp;
ina219_system_t *ina;
ultrasonic_system_t *ultrasonic;
} dashboard_context_t;
void dashboard_task_start(dashboard_context_t *ctx);
#endif
scheduler.c
#include "scheduler.h"
#include "esp_log.h"
static const char *TAG = "SCHEDULER";
static void scheduler_task(void *arg) {
scheduler_t *sched = (scheduler_t *)arg;
ESP_LOGI(TAG, "Scheduler task started");
while (1) {
TickType_t now = xTaskGetTickCount();
// Update all subsystems
if (sched->motors && sched->motors->update) {
sched->motors->update(sched->motors, now);
}
if (sched->adc && sched->adc->update) {
sched->adc->update(sched->adc, now);
}
if (sched->temp && sched->temp->update) {
sched->temp->update(sched->temp, now);
}
if (sched->ina && sched->ina->update) {
sched->ina->update(sched->ina, now);
}
if (sched->ultra && sched->ultra->update) {
sched->ultra->update(sched->ultra, now);
}
if (sched->mqtt && sched->mqtt->update) {
sched->mqtt->update(sched->mqtt, now);
}
if (sched->espnow && sched->espnow->update) {
sched->espnow->update(sched->espnow, now);
}
if (sched->ui && sched->ui->update) {
sched->ui->update(sched->ui, now);
}
vTaskDelay(pdMS_TO_TICKS(250)); // 20Hz update rate
}
}
void scheduler_init(scheduler_t *sched) {
ESP_LOGI(TAG, "Scheduler initialized");
}
void scheduler_start(scheduler_t *sched) {
xTaskCreate(scheduler_task, "scheduler", 8192, sched, 10, NULL);
ESP_LOGI(TAG, "Scheduler started");
}
scheduler.h
#ifndef SCHEDULER_H
#define SCHEDULER_H
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "motors.h"
#include "adc.h"
#include "temp_sensor.h"
#include "ina219_sensor.h"
#include "ultrasonic_sensor.h"
#include "mqtt_sys.h"
#include "espnow_sys.h"
#include "ui.h"
typedef struct {
motor_system_t *motors;
adc_system_t *adc;
temp_sensor_system_t *temp;
ina219_system_t *ina;
ultrasonic_system_t *ultra;
mqtt_system_t *mqtt;
espnow_system_t *espnow;
ui_system_t *ui;
} scheduler_t;
void scheduler_init(scheduler_t *sched);
void scheduler_start(scheduler_t *sched);
#endif
system_init.c
#include "system_init.h"
#include "nvs_flash.h"
#include "esp_log.h"
static const char *TAG = "SYSTEM_INIT";
void system_init(void) {
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "System initialization complete");
}
system_init.h
#ifndef SYSTEM_INIT_H
#define SYSTEM_INIT_H
void system_init(void);
#endif
CMakeLists.txt
idf_component_register(
SRCS "ESP-IDF_NovaGlide.c"
"system_init.c"
"scheduler.c"
"control_task.c"
"dashboard.c"
INCLUDE_DIRS "."
REQUIRES
esp_wifi
esp_netif
nvs_flash
motors
adc
wifi_sys
mqtt_sys
espnow_sys
sensors
controls
ui
i2c_bus)