- Published on
Complete Embedded Systems & IoT Programming Guide 2025: Firmware, RTOS, Protocols
- Authors

- Name
- Youngju Kim
- @fjvbn20031
Introduction: Why Embedded/IoT?
As of 2025, the global IoT device count has surpassed approximately 30 billion. From smart homes, industrial sensors, wearables, autonomous vehicles, and smart factories, embedded systems are core components of modern infrastructure. General application development and embedded development require fundamentally different mindsets. Memory is limited to KB, CPUs run at MHz, devices must operate for years on batteries, and they must satisfy real-time constraints.
This guide covers the full stack from embedded system fundamentals to IoT production systems: microcontroller selection, development environment, RTOS-based multitasking, hardware interfaces, sensor integration, IoT protocols, OTA, power management, Edge AI, and security.
1. Embedded Systems vs General Computing
1.1 Core Differences
| Item | General Computing | Embedded Systems |
|---|---|---|
| CPU speed | GHz | MHz (tens to hundreds) |
| RAM | GB | KB to MB |
| Storage | SSD/HDD TB | Flash KB to MB |
| OS | Linux/Windows/macOS | Bare-metal, RTOS, embedded Linux |
| Power | Tens to hundreds of watts | uA to mW |
| Real-time | None or weak | Strong (microsecond scale) |
| Lifetime | Years | 10-20 years |
| Updates | Frequent | OTA, rare |
1.2 Resource-Constrained Mindset
Embedded developers must always be aware of resource constraints.
- Memory allocation: Minimize malloc/free, prefer static allocation
- Stack size: Limit recursion, watch local array sizes
- CPU cycles: Integer over float, avoid unnecessary copies
- Power: Spend most time in sleep, wake via interrupts
1.3 Real-Time Requirements
Real-time systems must satisfy not only correctness but also time constraints.
- Hard Real-Time: Missed deadline = system failure (avionics, medical)
- Firm Real-Time: Missed deadline = invalid result (live video streaming)
- Soft Real-Time: Missed deadline = degraded quality (multimedia)
2. Microcontroller (MCU) Landscape
2.1 Major MCU Platforms
| MCU | CPU | RAM | Flash | Price | Features |
|---|---|---|---|---|---|
| Arduino Uno (ATmega328P) | 8-bit AVR 16MHz | 2KB | 32KB | $5-25 | Learning, simple |
| ESP32 | Xtensa dual-core 240MHz | 520KB | 4MB+ | $3-10 | Wi-Fi + BLE built-in |
| STM32F4 (Cortex-M4) | ARM 168MHz FPU | 192KB | 1MB | $5-15 | Industrial |
| STM32L0 (Cortex-M0+) | ARM 32MHz | 8KB | 64KB | $1-5 | Low power |
| Raspberry Pi Pico (RP2040) | Dual Cortex-M0+ 133MHz | 264KB | 2MB | $4 | Cheap, PIO |
| nRF52840 | Cortex-M4F 64MHz | 256KB | 1MB | $5-15 | BLE/Thread/Zigbee |
| ESP32-S3 | Xtensa dual-core 240MHz | 512KB | 8MB+ | $5-12 | Wi-Fi 6, AI accel |
2.2 MCU Selection Guide
- IoT + Wi-Fi: ESP32 or ESP32-S3
- Low-power battery: nRF52840, STM32L series
- High-perf control: STM32F4/F7, Teensy 4
- Learning/prototype: Arduino Uno, Pico
- Industrial: STM32F series, NXP iMX RT
2.3 SoC vs MCU vs SBC
- MCU: Single chip, KB-MB memory, bare-metal/RTOS, mW power
- SoC: Can run Linux, Raspberry Pi, higher power
- SBC: Raspberry Pi, BeagleBone, full Linux
3. Development Environments
3.1 Major IDEs/Toolchains
Arduino IDE (beginner-friendly)
// blink.ino
const int LED_PIN = 13;
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
Serial.println("Arduino started");
}
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(500);
digitalWrite(LED_PIN, LOW);
delay(500);
}
PlatformIO (multi-platform)
; platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
bblanchon/ArduinoJson @ ^7.0.0
knolleary/PubSubClient @ ^2.8
ESP-IDF (ESP32 native SDK)
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
static const char *TAG = "main";
void app_main(void) {
ESP_LOGI(TAG, "Hello from ESP32!");
while (1) {
ESP_LOGI(TAG, "Tick %lld", esp_timer_get_time() / 1000000);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
3.2 Debugging Tools
- JTAG/SWD: Hardware debuggers (ST-Link, J-Link, Black Magic Probe)
- GDB + OpenOCD: Open-source debug chain
- Serial Monitor: UART log output
- Logic Analyzer: Digital signal analysis
- Oscilloscope: Analog measurement
4. RTOS Fundamentals
4.1 RTOS vs Bare-metal vs Embedded Linux
| Item | Bare-metal | RTOS | Embedded Linux |
|---|---|---|---|
| Complexity | Low | Medium | High |
| Memory | Minimal | KB to hundreds of KB | MB+ |
| Multitasking | Super loop | Task-based | Process/thread |
| Determinism | High | High | Medium |
| Boot time | Instant | Milliseconds | Seconds |
| Example | Arduino | ESP32 | Raspberry Pi |
4.2 RTOS Core Concepts
- Task/Thread: Independent execution unit with priority
- Scheduler: Task selection (preemptive priority-based)
- Context Switching: Register save/restore on task switch
- Semaphore: Synchronization primitive (binary, counting)
- Mutex: Mutual exclusion (with priority inheritance)
- Queue: Thread-safe inter-task messaging
- Event Flag: Multi-event wait
- Software Timer: Periodic/one-shot timer callbacks
4.3 Priority Inversion
A low-priority task blocks a high-priority task. Solutions:
- Priority Inheritance: Temporarily raise mutex holder's priority
- Priority Ceiling: Assign maximum priority to the mutex
This is a famous bug that actually occurred on Mars Pathfinder in 1997.
5. FreeRTOS Deep Dive
5.1 Task Creation and Scheduling
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void sensor_task(void *pvParameters) {
while (1) {
float temp = read_temperature();
printf("Temperature: %.2f C\n", temp);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void led_task(void *pvParameters) {
int pin = (int)pvParameters;
gpio_set_direction(pin, GPIO_MODE_OUTPUT);
while (1) {
gpio_set_level(pin, 1);
vTaskDelay(pdMS_TO_TICKS(500));
gpio_set_level(pin, 0);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void app_main(void) {
xTaskCreate(sensor_task, "sensor", 4096, NULL, 5, NULL);
xTaskCreate(led_task, "led", 2048, (void*)2, 3, NULL);
}
5.2 Queue Example
QueueHandle_t xSensorQueue;
typedef struct {
float temperature;
float humidity;
uint32_t timestamp;
} sensor_data_t;
void producer_task(void *pv) {
sensor_data_t data;
while (1) {
data.temperature = read_temp();
data.humidity = read_humid();
data.timestamp = xTaskGetTickCount();
if (xQueueSend(xSensorQueue, &data, pdMS_TO_TICKS(100)) != pdPASS) {
printf("Queue full!\n");
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void consumer_task(void *pv) {
sensor_data_t data;
while (1) {
if (xQueueReceive(xSensorQueue, &data, portMAX_DELAY) == pdPASS) {
printf("Received: T=%.1f H=%.1f\n", data.temperature, data.humidity);
}
}
}
void app_main(void) {
xSensorQueue = xQueueCreate(10, sizeof(sensor_data_t));
xTaskCreate(producer_task, "prod", 4096, NULL, 5, NULL);
xTaskCreate(consumer_task, "cons", 4096, NULL, 4, NULL);
}
5.3 Semaphores and Mutexes
SemaphoreHandle_t xMutex;
SemaphoreHandle_t xBinarySemaphore;
int shared_counter = 0;
void task_a(void *pv) {
while (1) {
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
shared_counter++;
xSemaphoreGive(xMutex);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void IRAM_ATTR gpio_isr_handler(void *arg) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
5.4 Memory Management
FreeRTOS provides multiple heap implementations: heap_1 (alloc only), heap_2 (with free, fragmentation), heap_3 (malloc wrapper), heap_4 (with coalescing, recommended), heap_5 (multi-region).
void *ptr = pvPortMalloc(256);
if (ptr != NULL) {
vPortFree(ptr);
}
printf("Free heap: %u\n", xPortGetFreeHeapSize());
printf("Min ever: %u\n", xPortGetMinimumEverFreeHeapSize());
6. Zephyr RTOS
6.1 Zephyr Features
Zephyr is an open-source RTOS managed by the Linux Foundation with 450+ board support, device tree, Kconfig, POSIX compatibility, built-in BLE/802.15.4/LoRa/Thread, and West meta build tool.
6.2 Zephyr Hello World
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#define LED0_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
int main(void) {
if (!gpio_is_ready_dt(&led)) return 0;
gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
while (1) {
gpio_pin_toggle_dt(&led);
k_msleep(1000);
}
return 0;
}
6.3 Device Tree Overlay
/* boards/esp32.overlay */
/ {
aliases {
led0 = &my_led;
};
leds {
compatible = "gpio-leds";
my_led: led_0 {
gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
label = "Green LED";
};
};
};
7. Hardware Interfaces
7.1 GPIO
#include "driver/gpio.h"
void configure_gpio(void) {
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_POSEDGE,
.mode = GPIO_MODE_INPUT,
.pin_bit_mask = (1ULL << 4),
.pull_up_en = 1,
};
gpio_config(&io_conf);
gpio_install_isr_service(0);
gpio_isr_handler_add(4, gpio_isr_handler, (void*) 4);
}
7.2 I2C
Two-wire (SDA, SCL), supports multiple devices. Speeds: 100kHz/400kHz/1MHz/3.4MHz.
#include "driver/i2c.h"
#define I2C_MASTER_NUM 0
#define SENSOR_ADDR 0x76
esp_err_t i2c_master_init(void) {
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = 21,
.scl_io_num = 22,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 400000,
};
i2c_param_config(I2C_MASTER_NUM, &conf);
return i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
}
7.3 SPI
4-wire (MOSI/MISO/SCK/CS), full duplex, tens of MHz. Used for flash, SD cards, displays.
#include "driver/spi_master.h"
spi_device_handle_t spi;
void spi_init(void) {
spi_bus_config_t buscfg = {
.miso_io_num = 19,
.mosi_io_num = 23,
.sclk_io_num = 18,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4096,
};
spi_device_interface_config_t devcfg = {
.clock_speed_hz = 10 * 1000 * 1000,
.mode = 0,
.spics_io_num = 5,
.queue_size = 7,
};
spi_bus_initialize(HSPI_HOST, &buscfg, SPI_DMA_CH_AUTO);
spi_bus_add_device(HSPI_HOST, &devcfg, &spi);
}
7.4 UART
Asynchronous serial, start/stop bits, 9600-115200+ bps.
#include "driver/uart.h"
void uart_init(void) {
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
};
uart_param_config(UART_NUM_1, &uart_config);
uart_set_pin(UART_NUM_1, 17, 16, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
uart_driver_install(UART_NUM_1, 1024, 0, 0, NULL, 0);
}
7.5 ADC
#include "esp_adc/adc_oneshot.h"
adc_oneshot_unit_handle_t adc1_handle;
void adc_init(void) {
adc_oneshot_unit_init_cfg_t init = { .unit_id = ADC_UNIT_1 };
adc_oneshot_new_unit(&init, &adc1_handle);
adc_oneshot_chan_cfg_t config = {
.bitwidth = ADC_BITWIDTH_12,
.atten = ADC_ATTEN_DB_12,
};
adc_oneshot_config_channel(adc1_handle, ADC_CHANNEL_0, &config);
}
7.6 PWM
#include "driver/ledc.h"
void pwm_init(void) {
ledc_timer_config_t timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_13_BIT,
.timer_num = LEDC_TIMER_0,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK,
};
ledc_timer_config(&timer);
ledc_channel_config_t channel = {
.gpio_num = 18,
.duty = 4096,
};
ledc_channel_config(&channel);
}
8. Sensor Integration in Practice
8.1 Temperature/Humidity (DHT22, BME280)
#include <DHT.h>
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(115200);
dht.begin();
}
void loop() {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
Serial.printf("T: %.1f C, H: %.1f %%\n", t, h);
}
delay(2000);
}
8.2 Accelerometer/Gyroscope (MPU6050)
#include <Wire.h>
#include <MPU6050.h>
MPU6050 mpu;
void setup() {
Wire.begin();
mpu.initialize();
}
void loop() {
int16_t ax, ay, az, gx, gy, gz;
mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
Serial.printf("Accel: %d %d %d\n", ax, ay, az);
delay(100);
}
8.3 GPS (NEO-6M)
#include <TinyGPS++.h>
TinyGPSPlus gps;
void setup() {
Serial.begin(115200);
Serial2.begin(9600);
}
void loop() {
while (Serial2.available() > 0) {
if (gps.encode(Serial2.read())) {
if (gps.location.isValid()) {
Serial.printf("Lat: %.6f Lng: %.6f\n",
gps.location.lat(), gps.location.lng());
}
}
}
}
9. IoT Protocol Comparison
9.1 Protocol Matrix
| Protocol | Layer | Range | Bandwidth | Power | Topology | Use Cases |
|---|---|---|---|---|---|---|
| Wi-Fi | L1-L2 | 50-100m | High | High | Star | Home IoT |
| Bluetooth LE | L1-L2 | 10-50m | Medium | Low | Star/Mesh | Wearables |
| Zigbee | L1-L2 | 10-100m | Low | Low | Mesh | Smart home |
| Thread | L1-L3 | 10-100m | Low | Low | Mesh | Matter-based |
| LoRa/LoRaWAN | L1-L2 | 2-15km | Very low | Very low | Star | Smart city |
| NB-IoT | L1-L2 | Cellular | Low | Low | Cellular | Remote assets |
| MQTT | L7 | Any | - | - | Pub/Sub | General IoT |
| CoAP | L7 | Any | - | - | REST | Constrained |
| AMQP | L7 | Any | - | - | Queue | Enterprise |
| HTTP/REST | L7 | Any | - | - | Req/Res | General |
9.2 Wi-Fi Connection (ESP32)
#include "esp_wifi.h"
#include "esp_event.h"
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
printf("Got IP: " IPSTR "\n", IP2STR(&event->ip_info.ip));
}
}
void wifi_init_sta(void) {
esp_netif_init();
esp_event_loop_create_default();
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
wifi_config_t wifi_config = {
.sta = { .ssid = "MyWiFi", .password = "MyPassword" },
};
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
esp_wifi_start();
}
10. MQTT Deep Dive
10.1 MQTT Core Concepts
- Broker: Message mediator (Mosquitto, EMQX, HiveMQ)
- Client: Publisher or subscriber
- Topic: Hierarchical string (sensor/temperature/room1)
- QoS: 0 (at most once), 1 (at least once), 2 (exactly once)
- Retained Message: Broker stores, delivered to new subscribers immediately
- Last Will Testament (LWT): Auto-published on abnormal disconnect
10.2 ESP32 MQTT Client
#include "mqtt_client.h"
esp_mqtt_client_handle_t client;
static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data) {
esp_mqtt_event_handle_t event = event_data;
switch ((esp_mqtt_event_id_t)event_id) {
case MQTT_EVENT_CONNECTED:
printf("MQTT connected\n");
esp_mqtt_client_subscribe(client, "device/cmd", 1);
break;
case MQTT_EVENT_DATA:
printf("Topic: %.*s\n", event->topic_len, event->topic);
break;
default: break;
}
}
void mqtt_start(void) {
esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.uri = "mqtts://mqtt.example.com:8883",
.credentials.username = "device01",
.credentials.authentication.password = "secret",
.session.last_will.topic = "device/status",
.session.last_will.msg = "offline",
.session.last_will.qos = 1,
.session.last_will.retain = 1,
};
client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
esp_mqtt_client_start(client);
}
10.3 Topic Design Best Practices
- Hierarchical and intuitive: tenant/site/device/metric
- Use wildcards:
+(single level),#(multi-level) - Examples:
home/+/temperature,factory/line1/# - No PII
- Separate read/write paths:
device/+/cmdvsdevice/+/state
11. CoAP, LoRaWAN, BLE
11.1 CoAP
RFC 7252, REST style, UDP-based, very lightweight.
#include "coap3/coap.h"
void coap_client_example(void) {
coap_context_t *ctx = coap_new_context(NULL);
coap_address_t dst;
coap_address_init(&dst);
coap_session_t *session = coap_new_client_session(ctx, NULL, &dst, COAP_PROTO_UDP);
coap_pdu_t *pdu = coap_pdu_init(COAP_MESSAGE_CON, COAP_REQUEST_CODE_GET,
coap_new_message_id(session), coap_session_max_pdu_size(session));
coap_add_option(pdu, COAP_OPTION_URI_PATH, 4, (const uint8_t*)"temp");
coap_send(session, pdu);
}
11.2 LoRaWAN
Long range (km), low bandwidth (kbps), low power (years battery). Good for agriculture, environmental monitoring.
#include "LoRaWan_APP.h"
uint8_t devEui[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 };
void setup() {
Mcu.begin();
LoRaWAN.init(loraWanClass, loraWanRegion);
}
void loop() {
if (deviceState == DEVICE_STATE_SEND) {
prepareTxFrame(appPort);
LoRaWAN.send();
deviceState = DEVICE_STATE_CYCLE;
}
LoRaWAN.cycle(txDutyCycleTime);
}
11.3 BLE
#include <BLEDevice.h>
#include <BLEServer.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
BLECharacteristic *pCharacteristic;
void setup() {
BLEDevice::init("ESP32_Sensor");
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY
);
pService->start();
pServer->getAdvertising()->start();
}
void loop() {
float temp = readTemp();
pCharacteristic->setValue((uint8_t*)&temp, sizeof(temp));
pCharacteristic->notify();
delay(1000);
}
12. OTA Firmware Updates
12.1 OTA Basics
- A/B partitions: Current firmware (A) keeps running while new one (B) is written
- Rollback: Automatic fallback on boot failure or sanity check failure
- Security: HTTPS + signature verification required
- Interrupt-resilient: Must survive power loss during download
12.2 ESP-IDF OTA Example
#include "esp_ota_ops.h"
#include "esp_https_ota.h"
void ota_task(void *pv) {
esp_http_client_config_t config = {
.url = "https://ota.example.com/firmware.bin",
.cert_pem = (char *)server_cert_pem_start,
.timeout_ms = 5000,
};
esp_https_ota_config_t ota_config = { .http_config = &config };
esp_err_t ret = esp_https_ota(&ota_config);
if (ret == ESP_OK) {
printf("OTA successful. Restarting...\n");
esp_restart();
}
vTaskDelete(NULL);
}
void mark_valid_after_tests(void) {
esp_ota_img_states_t state;
const esp_partition_t *running = esp_ota_get_running_partition();
if (esp_ota_get_state_partition(running, &state) == ESP_OK) {
if (state == ESP_OTA_IMG_PENDING_VERIFY) {
if (self_tests_passed()) {
esp_ota_mark_app_valid_cancel_rollback();
} else {
esp_ota_mark_app_invalid_rollback_and_reboot();
}
}
}
}
12.3 Secure Boot + Flash Encryption
- Secure Boot: RSA/ECDSA signature on bootloader, firmware
- Flash Encryption: AES-256 encryption of external flash
- eFuse: Store keys in one-time programmable on-chip memory
13. Power Management
13.1 ESP32 Sleep Modes
| Mode | Current | RAM retained | Wake time | Wake sources |
|---|---|---|---|---|
| Active | 100-240 mA | Yes | - | - |
| Modem-Sleep | 3-20 mA | Yes | us | Auto |
| Light-Sleep | 0.8 mA | Yes | ms | Timer, GPIO, UART |
| Deep-Sleep | 10 uA | RTC only | Hundreds of ms | Timer, EXT0/1, Touch, ULP |
| Hibernation | 5 uA | No | From boot | RTC Timer, EXT1 |
13.2 Deep Sleep Example
#include "esp_sleep.h"
RTC_DATA_ATTR int boot_count = 0;
void app_main(void) {
boot_count++;
printf("Boot count: %d\n", boot_count);
collect_and_send();
esp_sleep_enable_timer_wakeup(30 * 60 * 1000000ULL);
esp_sleep_enable_ext0_wakeup(GPIO_NUM_25, 0);
printf("Entering deep sleep\n");
esp_deep_sleep_start();
}
13.3 Battery Life Calculation
Battery: 2000 mAh
Avg current = (Active_mA * Active_time + Sleep_mA * Sleep_time) / Total_time
Example: 5s active (100 mA) + 30 min sleep (0.01 mA)
Avg = (100*5 + 0.01*1800) / 1805 = 0.29 mA
Life = 2000 / 0.29 / 24 = ~287 days
14. Edge AI on MCU
14.1 TensorFlow Lite Micro
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "model_data.h"
constexpr int kTensorArenaSize = 100 * 1024;
uint8_t tensor_arena[kTensorArenaSize];
void setup() {
const tflite::Model* model = tflite::GetModel(g_model);
static tflite::MicroMutableOpResolver<5> resolver;
resolver.AddConv2D();
resolver.AddMaxPool2D();
resolver.AddFullyConnected();
resolver.AddSoftmax();
resolver.AddRelu();
static tflite::MicroInterpreter interpreter(
model, resolver, tensor_arena, kTensorArenaSize);
interpreter.AllocateTensors();
TfLiteTensor* input = interpreter.input(0);
for (int i = 0; i < input->bytes; i++) {
input->data.uint8[i] = sensor_reading[i];
}
interpreter.Invoke();
TfLiteTensor* output = interpreter.output(0);
}
14.2 Edge Impulse
No-code ML pipeline tool: data collection, preprocessing, training, MCU deployment automated.
15. Security
15.1 Embedded Security Checklist
- Enable Secure Boot (signed firmware only)
- Enable Flash Encryption (protect sensitive data)
- TLS 1.2+ for all network traffic
- Verify TLS certificate chain
- Use hardware crypto accelerators (AES, SHA, RSA/ECC)
- Disable debug ports in production (JTAG/SWD)
- Sign firmware (ECDSA), verify in bootloader
- Use TRNG for random numbers
- Enable key rotation via OTA
- Least privilege, disable unused services/ports
15.2 Mutual TLS Example
esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.uri = "mqtts://broker.example.com:8883",
.broker.verification.certificate = (const char*)server_ca_pem,
.credentials.authentication.certificate = (const char*)client_cert_pem,
.credentials.authentication.key = (const char*)client_key_pem,
};
16. Production Considerations
16.1 Logging and Monitoring
- Local log levels: DEBUG/INFO/WARN/ERROR
- Remote log transmission only on anomaly (bandwidth save)
- Telemetry: CPU/memory/battery/signal strength
- Crash dumps: ESP32 Core Dump, store on flash and transmit
16.2 Device Lifecycle
- Provisioning: Factory setup
- Onboarding: User Wi-Fi/account linking (BLE, SoftAP, WPS)
- Operation: Normal operation, OTA updates
- Decommissioning: Delete keys, factory reset
16.3 Manufacturing and Volume
- Partition table: bootloader, app, NVS, OTA, user data
- MAC-based unique ID
- Automated test fixtures with JTAG + test firmware
- Certifications: FCC, CE, IC, KC
17. Practical Project Ideas
- Environment Monitor: ESP32 + BME280 + MQTT → Grafana dashboard
- Smart Doorlock: ESP32 + RFID/fingerprint + BLE mobile app
- Farm Sensor Network: LoRaWAN + soil moisture + gateway
- Wearable Heart Monitor: nRF52 + MAX30102 + BLE
- Voice-Controlled Hub: ESP32-S3 + microphone + TFLite Micro
- Factory Asset Tracking: LoRaWAN + GPS + NB-IoT fallback
18. Quiz
Q1. What are two solutions to priority inversion in FreeRTOS?
A: Priority Inheritance and Priority Ceiling. The former temporarily raises the priority of a mutex holder to that of the highest-priority waiter; the latter pre-assigns a maximum priority to the mutex when created.
Q2. What are the differences between MQTT QoS levels 0, 1, and 2?
A: QoS 0 is "at most once" (fire and forget, may be lost), QoS 1 is "at least once" (requires PUBACK, may duplicate), QoS 2 is "exactly once" (4-way handshake, slowest). Sensor telemetry typically uses 0 or 1; critical commands use 2.
Q3. Why do OTA updates use A/B partitions?
A: New firmware is written to a separate partition (B) while the current one (A) keeps running. On failure, automatic rollback to the previous firmware occurs. The device cannot be bricked by power loss during download.
Q4. What are the main differences between I2C and SPI?
A: I2C uses 2 wires (SDA/SCL), addresses distinguish devices, speeds 100kHz-3.4MHz, relatively slow. SPI uses 4 wires (MOSI/MISO/SCK/CS), CS line selects devices, full-duplex, tens of MHz. Simple multi-sensor setups use I2C; high-speed displays/flash use SPI.
Q5. How do you persist data across ESP32 deep sleep?
A: Use the RTC_DATA_ATTR macro to place variables in RTC memory (8KB limit), which survives deep sleep. Example: RTC_DATA_ATTR int boot_count = 0;. For larger data, use NVS (Non-Volatile Storage) or flash.
19. References
- FreeRTOS Documentation - freertos.org
- Zephyr Project Documentation - docs.zephyrproject.org
- ESP-IDF Programming Guide - docs.espressif.com
- STM32 HAL and Low-Layer Driver - st.com
- MQTT 5.0 Specification - docs.oasis-open.org
- CoAP RFC 7252 - datatracker.ietf.org
- LoRa Alliance Technical Docs - lora-alliance.org
- TensorFlow Lite Micro - tensorflow.org/lite/microcontrollers
- Edge Impulse Docs - docs.edgeimpulse.com
- Arm Mbed OS Documentation - os.mbed.com
- Making Embedded Systems - Elecia White, O'Reilly
- Mastering STM32 - Carmine Noviello
- The Hardware Hacker - Andrew Huang
Conclusion
Embedded/IoT development sits at the intersection of hardware, real-time constraints, networking, and security. Starting from Arduino and evolving through ESP32/STM32/Zephyr, developers can build production-grade systems with robust OTA, security, and power management. In 2025, Matter, Thread, and Edge AI standards are maturing, expanding opportunities for embedded developers. From blinking a single LED to networking entire cities, the journey is limitless.