SmartDB Logo

SmartDB v2

  • SmartDB Firebase API and team integration
    • Run locally
    • Frontend handoff
    • Ownership and device installation
    • Delivery, configuration and commands
    • Firebase deployment
    • Source and verification
  • Current v2 system architecture
    • Responsibilities and data flow
    • Hardware and firmware boundary
    • Local control and fusion
      • Fusion data flow
      • Local protection state machine
    • Cloud and frontend boundary
      • Telemetry and preview delivery
      • Cloud command application
    • Validation and remaining production work
  • SmartDB developer console
  • SmartDB v2 system firmware
    • Easy setup — one terminal menu
      • What each menu item does
      • Where configuration lives
      • Setup troubleshooting
    • Manual build and flash (optional)
    • Local sensing and protection
    • RGB–thermal fusion
    • Firebase integration
    • Postman and telemetry
    • Verification and source layout
  • SmartDB v2 — permanent compact project
    • Current revision
    • Firmware and setup
    • Files and previews
    • Previous revision
  • SmartDB compact redesign — board routing complete
    • Saved work
    • Connector access and routing
    • STEP export
    • Two-camera connections
    • Validation status and remaining work
    • Resuming the editing scripts
  • SmartDB Postman Collection Guide
    • Protocol boundary
      • Implemented v2 bench firmware
    • Import and environment setup
    • Recommended workflow
    • Collection coverage
      • Authentication
      • Users and notifications
      • Devices and configuration
      • Telemetry, alerts, and commands
    • Status and error conventions
    • Maintaining the collection
  • SmartDB Device Ingestion Gateway
    • Status and security boundary
    • Quick start
    • Resources
    • Configuration
    • Development state model
    • Production checklist
  • SmartDB
    • Household Electrical Fault Detection & Protection System
      • Full Engineering Development Specification
    • Document Control
      • Current v2 bench implementation — 10 October 2026
      • Revision History
      • Normative Language
    • Contents
    • Executive Summary
      • Release 1 Product Decisions
      • Primary Deliverables
    • Product Definition and Scope
      • Core Use Case
      • Actors
      • In Scope for Release 1
      • Explicitly Out of Scope (Release 1)
      • Functional Requirements
      • Non-Functional Targets
    • Reference Architecture
      • Ownership Boundary
      • Data Classification
      • Shared Engineering Rules
    • PART A — Embedded/Edge Engineering Specification
      • ESP32 sensing, signal processing, rule-based safety layer, relay control, and security
    • Embedded/Edge Team Scope
      • Team Responsibilities
      • Edge Team Does Not Own
    • Firmware Architecture
      • Repository Layout
      • Edge Processing Pipeline
      • Rule-Based Threshold Engine (Independent Safety Layer)
      • Relay Control
      • Security
      • Offline/Battery-Backed Behavior
      • Power Subsystem
    • Mandatory Edge Test Cases
    • PART B — Cloud AI Inference Engineering Specification
      • Isolation Forest, YOLO RGB detection, thermal anomaly analysis, and decision fusion
    • AI Inference Team Scope
      • Responsibilities
      • AI Pipeline
      • RGB and thermal fusion design — 2026-10-10
      • Fusion and Decision Rules
      • Model Governance
    • AI/Vision Verification and Acceptance
    • PART C — Cloud Backend Engineering Specification
      • Firebase: ingestion, storage, alert routing, device registry
    • Cloud Backend Team Scope
      • Responsibilities
      • Cloud Team Does Not Own
    • Core Cloud Data Model
    • Cloud API/Event Contract
      • Illustrative backend-enriched event
      • Cloud Function Responsibilities
    • Cloud Security and Reliability
    • Cloud Verification and Acceptance
    • PART D — Mobile Application Engineering Specification
      • Flutter app
    • Mobile App Scope
      • Responsibilities
      • Key Screens
      • Alert UX Rules
    • Mobile Verification and Acceptance
    • PART E — Shared Interface, Delivery, and Program Management
    • Ownership Matrix (RACI)
    • Hardware Procurement Summary
      • Hardware Bill of Materials
      • Software Costs
      • Software Stack
    • Project Plan and Timeline
    • End-to-End Acceptance Tests
    • Evaluation Plan
      • Success Criteria
      • Data Collection
    • Risk Register
      • Mitigation Strategy (High-Priority Risks)
    • Stakeholders
      • Primary Beneficiaries
      • Secondary Beneficiaries
      • Stakeholder Engagement
    • Open Decisions Before Field Pilot
    • Device Ingestion API — Production CoAP/DTLS Contract
      • Versioned Resources and Methods
      • Common Envelope and Origin Boundary
      • Examples (JSON Rendering of the CBOR Model)
      • Delivery, ACKs, Retry, and Reconnect
      • Offline Queue, Ordering, and Datagram Size
      • Idempotency, Replay, Reboot, and Time
      • Responses and Errors
      • PSK Lifecycle, Configuration Trust, and Rate Limits
      • Observability and Safety Invariant
    • Repository Implementation, API Tooling, and Operations
      • Repository Map and Sources of Truth
      • Implemented Device Gateway
      • Gateway Runbook
      • HTTP API and Postman Collection Rules
      • Verification Matrix
      • Known Production Gaps and Required Closure
    • Conclusion
      • Immediate Next Steps
    • References
  • SmartDB
    • Project links
    • Repository map
    • Run the backend locally
    • Set up the two boards
    • Current hardware
    • Project maturity
    • Code style
    • Related projects

Code and API reference

  • Firmware code guide
    • Build structure
    • Where to change behavior
    • Startup and task boundaries
    • Fusion and measurement
    • Configuration ownership
    • Source interfaces and verification
  • C interfaces
    • Fusion core
    • Protection core
    • System control
    • Connection settings
    • Telemetry publisher
  • Protocol implementation
    • USB dispatcher
    • HTTPS telemetry worker
  • Local HTTP API
    • Routes and responses
    • Read status
    • Replace control configuration
    • Open the relay
    • Retrieve images
    • Contract and examples
  • USB setup protocol
    • Framing
    • Operations
    • Read and update control limits
    • Connection settings
  • Telemetry and central API
    • Configure delivery
    • Payload
    • Queue, retry and server responsibilities
    • Other central routes

Related projects

  • SmartDB project directory
  • ByteLab
    • Browser edition
    • Available features and SmartDB fit
    • Install and open the project
    • Inspect a SmartDB board over USB
    • Telemetry integration boundary
    • ByteLab source map

Development reference

  • PlatformIO setup for SmartDB v2
    • Interactive setup
    • Build targets
    • Configuration and recovery
  • Workflow for ESP-IDF
    • Enter your project
    • Load ESP-IDF environment (if not already loaded)
    • Configure project (optional)
    • Build
    • Find ESP32 serial port
    • Flash firmware
    • Monitor serial output
    • Normal development loop
    • CMake errors
    • Before committing to Git
  • Blink LED: ESP-IDF Edition
    • Purpose
    • Development Environment
      • Host Machine
    • ESP-IDF Environment Setup
    • Load ESP-IDF
    • Creating ESP-IDF Project
    • Code
      • Explanation
    • Overall execution flow
      • Why using Preprocessor
      • Why use pointers
      • Why use struct
      • How they work together
    • Project Migration to main.c
    • Build System Verification
    • Issue: Missing app_main
      • Error
      • Cause
      • Solution
    • Issue: Missing Source File
      • Error
      • Cause
      • Solution
    • LED Blink Implementation
    • Issue: Unsupported LED Type
      • Error
      • Cause
      • Solution: Simplify GPIO Control
    • Final Firmware Structure
    • Build Workflow
      • Development cycle
    • Hardware Connection
    • Clean Build
  • LED Blink API Reference: ESP-IDF edition
    • GPIO: General Purpose Input Output
      • esp_err_t gpio_reset_pin(gpio_num_t gpio_num)
      • esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level)
      • esp_err_t gpio_set_direction(gpio_num_t gpio_num, gpio_mode_t mode)
    • LED Strip (led_strip)
      • Allocate LED Strip Object with RMT Backend
      • Allocate LED Strip Object with SPI Backend
      • function led_strip_clear
      • function led_strip_refresh
      • function led_strip_set_pixel
      • struct led_strip_rmt_config_t
      • function led_strip_new_rmt_device
      • struct led_strip_spi_config_t
      • function led_strip_new_spi_device
  • Preprocessor
    • File Inclusion
    • Macro Substitution
    • Conditional Inclusion
  • Pointer and Smart Pointer
    • Pointer
    • Pointer as Arguments
    • Pointers and Arrays
  • YOLOv26 – Architectural Overview
    • Convolutional Neural Network (CNN)
      • Layers of a CNN
      • How a CNN Works
      • Feature Extraction
    • CV: Computer Vision
      • Image Classification
      • Object Localization
      • Object Detection
      • Image Segmentation
      • Relationship Between Tasks
    • Object Detection
      • Detection pipeline
      • Bounding Box
      • Confidence score and objectness
      • Class Probability
      • Intersection over Union (IoU)
      • Non-Maximum Suppression (NMS)
    • YOLO Architecture
      • One-Stage Detection
      • Backbone Network
      • Neck (Feature Aggregation Module)
      • Detection Head
      • Multi-scale Predition
    • YOLOv26
      • Architectural Improvement
      • New Modules
      • Loss Function
      • Training Strategy
  • Jetson Orin Nano Configuration
    • System Requirements
      • Host Machine (for SDK Manager)
      • Target Device
    • Installation
      • Prepare Host Machine
      • Install SDK Manager
      • Flash JetPack SDK
      • Verify Installation
    • Environment Configuration
      • First boot
      • Install Jetson Monitoring tools
      • Install NVIDIA container runtime
      • Install NVIDIA container runtime:
      • Install NVIDIA container toolkit
      • Create YOLO workspace
      • Test YOLO
      • Optimize for Jetson
      • Camera setup
      • SmartDB recommended architecture
  • SmartDB ESP32-CAM diagnostic firmware
    • Current status
    • Hardware assumptions
    • Build, flash, and monitor
    • Configuration
    • How to use it
    • Expected serial output
    • Latest monitor log explained
    • Code explanation
      • Headers and SCCB declarations
      • Compile-time options and pin definitions
      • camera_config
      • SD state: sd_card and sd_mounted
      • sd_init()
      • wifi_init_softap()
      • gpio_level_check()
      • sccb_debug_scan()
      • camera_init()
      • capture_frame()
      • app_main() and complete control flow
    • Troubleshooting
    • Project layout
  • Documentation website
    • Publish to Vercel
    • Architecture diagrams
SmartDB
  • Blink LED: ESP-IDF Edition

Blink LED: ESP-IDF Edition

Purpose

This document describes the complete setup process for developing an ESP32 firmware project using ESP-IDF.

The goal of this project is to establish a working ESP-IDF development environment before moving to SmartDB firmware development.

This includes:

  • ESP-IDF installation verification

  • Toolchain configuration

  • Project creation

  • Component dependency setup

  • Build system configuration

  • Firmware flashing

  • Debugging workflow

Development Environment

Host Machine

Component

Version

OS

Ubuntu 24.04

Framework

ESP-IDF v6.x

Build System

CMake + Ninja

Language

C/C++

Python Environment

ESP-IDF managed Python

ESP-IDF location:

~/esp/esp-idf

Project location:

~/projects/SmartDB/esp_idf/blink

ESP-IDF Environment Setup

Load ESP-IDF

Before using ESP-IDF commands:

source ~/esp/esp-idf/export.sh

This configures:

  • ESP-IDF tools

  • Compiler

  • Python environment

  • CMake integration

  • Ninja build system

Verify:

idf.py --version

Expected:

ESP-IDF v6.x

Creating ESP-IDF Project

Create project:

idf.py create-project blink

Enter directory:

cd blink

Initial structure:

blink/
├── CMakeLists.txt
└── main/
    ├── CMakeLists.txt
    └── blink.c

Code

#include <stdio.h>
#include <stdbool.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "driver/gpio.h"
#include "esp_log.h"
#include "led_strip.h"
#include "sdkconfig.h"

static const char *TAG = "blink";

#define BLINK_GPIO CONFIG_BLINK_GPIO

static uint8_t s_led_state = 0;

#ifdef CONFIG_BLINK_LED_STRIP
    static led_strip_handle_t led_strip;

    static void blink_led(void) {
        if (s_led_state) {
            led_strip_set_pixel(led_strip, 0, 16, 16, 16);
            led_strip_refresh(led_strip);
        } else {
            led_strip_clear(led_strip);
        }
    }

    static void configure_led(void) {
        ESP_LOGI(TAG, "Configuref to blink addressable LED");
        led_strip_config_t strip_config = {
            .strip_gpio_num = BLINK_GPIO,
            .flags,with_dma = true
        };

        #if CONFIG_BLINK_LED_STRIP_BACKEND_RMT
            led_strip_rmt_config_t rmt_config = {
                .resolution_hz = 10 * 1000 * 1000,
                .flags.with_dma = false
            };
            ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
        #elif CONFIG_BLINK_LED_STRIP_BACKEND_SPI
            led_strip_spi_config_t spi_config = {
                .spi_bus = SPI2_HOST,
                .flags.with_dma = true
            };
            ESP_ERROR_CHECK(led_strip_new_spi_device(&strip_config, &spi_config, &led_strip));
        #else
        #error "unsupported LED strip backend"
        #endif 

        led_strip_clear(led_strip);
    }
#elif CONFIG_BLINK_LED_GPIO
    static void configure_led(void) {
        gpio_reset_pin(BLINK_GPIO);
        gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
    }

    static void blink_led(void) {
        gpio_set_level(BLINK_GPIO, s_led_state);
    }
#else
#error "unsupported LED type"
#endif 

void app_main(void) {
    configure_led();

    while (1) {
        ESP_LOGI(TAG, "Turning the LED %s!", s_led_state == true ? "ON": "OFF");
        blink_led();
        s_led_state = !s_led_state;
        vTaskDelay(CONFIG_BLINK_PERIOD/portTICK_PERIOD_MS);
    }
}

Explanation

This is a typical ESP-IDF Blink LED example, but it is more advanced than a simple GPIO blink because it supports two types of LEDs:

  1. Addressable LED strip (WS2812 / NeoPixel type)

  2. Normal GPIO LED (on/off LED)

The code uses compile-time configuration (sdkconfig) to decide which implementation gets compiled.

Header files

#include <stdio.h>
#include <stdbool.h>

Standard C libraries.

stdio.h

Provides standard input/output functions:

printf()

ESP-IDF often uses its own logging system instead:

ESP_LOGI()

so this is not actually used here.

stdbool.h

Allows use of:

bool
true
false

Example:

bool led_on = true;

Instead of:

int led_on = 1;
FreeRTOS headers
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

ESP-IDF runs on FreeRTOS.

FreeRTOS provides:

  • tasks

  • delays

  • scheduling

  • synchronization

This line:

vTaskDelay()

comes from:

freertos/task.h

Example:

vTaskDelay(1000 / portTICK_PERIOD_MS);

means:

Pause this task for 1000 milliseconds.

GPIO driver
#include "driver/gpio.h"

Provides ESP32 GPIO control.

Functions used:

gpio_reset_pin()
gpio_set_direction()
gpio_set_level()

Example:

gpio_set_level(GPIO_NUM_2, 1);

sets GPIO 2 HIGH.

Logging

#include "esp_log.h"

ESP-IDF logging system.

Instead of:

printf("LED ON");

ESP-IDF uses:

ESP_LOGI(TAG,"LED ON");

Output:

I (1234) blink: LED ON

The format:

LEVEL (time) TAG: message
LED strip driver
#include "led_strip.h"

This is for addressable LEDs.

Example:

  • WS2812B

  • NeoPixel

  • SK6812

These LEDs are not simple ON/OFF.

They receive data:

ESP32
 |
 | single wire
 |
WS2812
 |
RGB LED

You send:

red = 16
green = 16
blue = 16

and the LED generates that color.

ESP-IDF configuration
#include "sdkconfig.h"

This includes settings generated by:

idf.py menuconfig

Example:

CONFIG_BLINK_GPIO=2
CONFIG_BLINK_PERIOD=1000
CONFIG_BLINK_LED_GPIO=y

After compiling, these become C macros.

Example:

#define BLINK_GPIO CONFIG_BLINK_GPIO

becomes:

#define BLINK_GPIO 2

Global variables

Logging tag
static const char *TAG = "blink";

Used here:

ESP_LOGI(TAG,"...");

static means this variable is only visible inside this file.

LED GPIO
#define BLINK_GPIO CONFIG_BLINK_GPIO

Instead of hardcoding:

#define BLINK_GPIO 2

ESP-IDF lets you configure it.

Example:

menuconfig
      |
      v
CONFIG_BLINK_GPIO=2
      |
      v
BLINK_GPIO

LED state

static uint8_t s_led_state = 0;

Stores current LED state.

Initially:

s_led_state = 0

meaning:

OFF

Later:

s_led_state = !s_led_state;

toggles:

0 -> 1
1 -> 0

Compile-time decision

This is the important part:

#ifdef CONFIG_BLINK_LED_STRIP

means:

“If addressable LED support is enabled, compile this section.”

Otherwise:

#elif CONFIG_BLINK_LED_GPIO

compile GPIO version.

Otherwise:

#error "unsupported LED type"

stop compilation.

Case 1: Addressable LED
#ifdef CONFIG_BLINK_LED_STRIP

Example:

WS2812 LED.

LED object
static led_strip_handle_t led_strip;

This stores the LED strip driver instance.

Think:

led_strip
    |
    +-- GPIO
    +-- protocol
    +-- buffer

Blink function

static void blink_led(void)

Controls the LED.

Turn ON
if (s_led_state)

If state = 1:

led_strip_set_pixel(led_strip, 0, 16, 16, 16);

Arguments:

LED index
      |
      v
      0

RGB:

R = 16
G = 16
B = 16

So:

white dim light

Then:

led_strip_refresh()

actually sends the data.

Turn OFF
led_strip_clear(led_strip);

sets all LEDs:

RGB = 0,0,0
Case 2: Normal GPIO LED
#elif CONFIG_BLINK_LED_GPIO

This is the simple one.

Configure GPIO
static void configure_led(void) {
    gpio_reset_pin(BLINK_GPIO);
    gpio_set_direction(
        BLINK_GPIO,
        GPIO_MODE_OUTPUT
    );
}

Meaning:

Reset GPIO:

GPIO2
 |
 reset

Then:

GPIO2 = OUTPUT

because we want to drive the LED.

Blink GPIO
static void blink_led(void) {
    gpio_set_level(
        BLINK_GPIO,
        s_led_state
    );
}

If:

s_led_state = 1

then:

GPIO2 = HIGH
LED ON

If:

s_led_state = 0

then:

GPIO2 = LOW
LED OFF

app_main()

This is the ESP-IDF equivalent of:

main()

in normal C.

ESP-IDF starts FreeRTOS first, then calls:

app_main()
Configure LED
configure_led();

Depending on config:

Either:

GPIO setup

or:

LED strip driver setup
Infinite loop
while(1)

Embedded systems usually run forever.

Print status
ESP_LOGI(TAG, "Turning the LED %s!", s_led_state == true ? "ON":"OFF");

This uses the ternary operator:

Equivalent:

if(s_led_state) {
    print("ON");
} else {
    print("OFF");
}
Blink
blink_led();

Actually changes hardware.

Toggle state
s_led_state = !s_led_state;

Example:

Before:

s_led_state = 0

After:

s_led_state = 1

Next loop:

1 -> 0
Delay
vTaskDelay(
CONFIG_BLINK_PERIOD / portTICK_PERIOD_MS
);

Example:

CONFIG_BLINK_PERIOD = 1000

wait:

1000 ms = 1 second

Overall execution flow

ESP32 Boot
    |
    v
FreeRTOS starts
    |
    v
app_main()
    |
    v
configure_led()
    |
    +----------------+
    |                |
 GPIO LED       LED Strip
    |                |
    v                v
set GPIO       setup driver
output         RMT/SPI
    |
    v
while(1)
    |
    v
LED ON
    |
    v
wait
    |
    v
LED OFF
    |
    v
wait
    |
    v
repeat

Your firmware will likely follow the simpler pattern:

app_main()
 |
 +-- initialize drivers
 |
 +-- initialize WiFi
 |
 +-- initialize sensors
 |
 +-- create FreeRTOS tasks
 |
 +-- forever
       |
       +-- read sensor
       +-- process data
       +-- send MQTT

This blink example is basically showing the ESP-IDF architecture pattern: initialize hardware → run tasks forever.

Why using Preprocessor

The preprocessor runs before compilation. It modifies your source code based on conditions or substitutions.

For example:

#define BLINK_GPIO CONFIG_BLINK_GPIO

If CONFIG_BLINK_GPIO is 2, the compiler effectively sees:

#define BLINK_GPIO 2

No variable is created; it’s just text replacement.

Conditional compilation

#ifdef CONFIG_BLINK_LED_GPIO
    // GPIO implementation
#elif CONFIG_BLINK_LED_STRIP
    // LED strip implementation
#else
#error "unsupported LED type"
#endif

Only one branch is compiled.

Suppose sdkconfig contains:

#define CONFIG_BLINK_LED_GPIO 1

The compiler effectively sees:

// GPIO implementation

The LED strip code is completely ignored—it isn’t even compiled.

Why not use if

Imagine writing:

if (led_type == GPIO) {
    gpio_set_level(...);
} else {
    led_strip_refresh(...);
}

The compiler would have to compile both implementations.

On a microcontroller:

  • More flash memory used

  • Larger firmware

  • Longer compile time

  • Unnecessary dependencies

Using the preprocessor removes unused code entirely.

Why use pointers

A pointer stores the memory address of something.

Example:

int x = 10;
int *p = &x;

Memory:

Address      Value

0x1000 ----> 10
              ^
              |
              p

p doesn’t store 10; it stores where 10 lives.

Why ESP-IDF uses pointers

Look at:

static led_strip_handle_t led_strip;

This is a handle (internally it’s essentially a pointer to a driver object).

Later:

ESP_ERROR_CHECK(
    led_strip_new_rmt_device(
        &strip_config,
        &rmt_config,
        &led_strip
    )
);

Notice the &.

You’re passing the address of led_strip.

The function can then write into that variable:

Before

led_strip
+------+
| NULL |
+------+

        |
        | function
        v

After

led_strip
+-----------+
| 0x3FFB100 |
+-----------+

Now led_strip refers to an initialized driver object.

Without pointers, the function couldn’t modify the caller’s variable directly.

Another example

Suppose you have:

void set(int x) {
    x = 100;
}

Calling:

int value = 5;
set(value);

does not change value, because x is a copy.

With a pointer:

void set(int *x) {
    *x = 100;
}

Calling:

set(&value);

changes the original variable.

Why use struct

A struct groups related information into one object.

Without a struct:

int gpio;
bool dma;
int resolution;

You now have three separate variables that belong together.

Instead:

typedef struct {
    int gpio;
    bool dma;
    int resolution;
} config_t;

Now:

config_t cfg;

contains everything.

In ESP-IDF

Example:

led_strip_config_t strip_config = {
    .strip_gpio_num = BLINK_GPIO,
    .flags.with_dma = true
};

Think of it as:

strip_config

+----------------------+
| strip_gpio_num = 2   |
+----------------------+
| flags                |
|   with_dma = true    |
+----------------------+

The driver receives one configuration object instead of many separate arguments.

Imagine if the function looked like this:

led_strip_new(
    gpio,
    dma,
    resolution,
    brightness,
    pixel_count,
    backend,
    invert,
    ...
);

That quickly becomes hard to read and maintain.

Instead:

led_strip_new(&strip_config);

The configuration can grow over time without changing the function signature.

Why the . operator?

You access struct members with .:

strip_config.strip_gpio_num = 2;

Meaning:

strip_config
      |
      +-- strip_gpio_num

Why .member = value?

ESP-IDF often initializes structs like this:

led_strip_config_t cfg = {
    .strip_gpio_num = 2,
    .flags.with_dma = true
};

These are designated initializers.

Compared to:

led_strip_config_t cfg = {2, true};

the designated version is much clearer because you immediately know what each value represents.

How they work together

In the blink example, all three features complement each other:

sdkconfig
    │
    ▼
Preprocessor (#ifdef)
    │
    ▼
Select GPIO or LED strip implementation
    │
    ▼
Create configuration struct
    │
    ▼
Pass the struct's address (pointer)
    │
    ▼
Driver initializes the hardware

This combination is one of the reasons C remains the dominant language for embedded systems: the preprocessor allows compile-time customization, structs organize related configuration cleanly, and pointers let functions efficiently work with hardware resources and large data structures without unnecessary copying.

Project Migration to main.c

The default generated project referenced:

main/blink.c

However, ESP-IDF applications require an entry function:

void app_main(void)

The source file was renamed:

blink.c
    |
    v
main.c

Updated:

main/CMakeLists.txt

from:

idf_component_register(SRCS "blink.c")

to:

idf_component_register(
    SRCS "main.c"
    INCLUDE_DIRS "."
)

Build System Verification

First build:

idf.py build

The project successfully generated:

  • bootloader

  • partition table

  • application ELF

However, linking failed:

undefined reference to `app_main'

Issue: Missing app_main

Error

undefined reference to app_main

Cause

ESP-IDF FreeRTOS startup calls:

app_main()

but the compiled source did not contain this function.

Solution

Added:

void app_main(void) {}

to:

main/main.c

After rebuilding:

idf.py build

The linker error was resolved.

Issue: Missing Source File

Error

Cannot find source file:

main/blink.c

Cause

CMake still referenced the old filename.

Solution

Updated:

main/CMakeLists.txt

with:

idf_component_register(
    SRCS "main.c"
    INCLUDE_DIRS "."
)

Then cleaned CMake cache:

idf.py fullclean

Rebuilt:

idf.py build

LED Blink Implementation

Initial example used ESP-IDF configurable LED abstraction:

CONFIG_BLINK_LED_GPIO
CONFIG_BLINK_LED_STRIP
CONFIG_BLINK_PERIOD

These values are generated from:

sdkconfig

through:

idf.py menuconfig

Issue: Unsupported LED Type

Error

#error "unsupported LED type"

and:

CONFIG_BLINK_PERIOD undeclared

Cause

The example expected menuconfig options that were not enabled.

The code contained:

#if CONFIG_BLINK_LED_GPIO

#elif CONFIG_BLINK_LED_STRIP

#else
#error "unsupported LED type"
#endif

No LED type was selected.

Solution: Simplify GPIO Control

For the first SmartDB firmware prototype, the configurable example was replaced with direct GPIO control.

Implemented:

gpio_reset_pin()
gpio_set_direction()
gpio_set_level()

This removes dependency on:

CONFIG_BLINK_*

and creates a simpler firmware base.

Final Firmware Structure

blink/

├── CMakeLists.txt
├── sdkconfig
└── main/
    ├── CMakeLists.txt
    └── main.c
...

Build Workflow

Development cycle

After modifying code:

idf.py build

Flash:

idf.py -p /dev/ttyUSB0 flash

Monitor:

idf.py -p /dev/ttyUSB0 monitor

Combined:

idf.py build flash monitor

Hardware Connection

Find ESP32 device:

ls /dev/ttyUSB*

Example:

/dev/ttyUSB0

If permission denied:

sudo usermod -aG dialout $USER

Restart session.

Clean Build

If CMake cache becomes corrupted:

idf.py fullclean
idf.py build
Previous Next

© Copyright 2026, SmartDB.

Built with Sphinx using a theme provided by Read the Docs.