esp32 bootloader embedded firmware iot
Configuración de Bootloader ESP32: Guía Completa
Aprende a configurar y personalizar bootloaders para microcontroladores ESP32, incluyendo particiones, OTA y recuperación de firmware.
Por Jesus Velez
Configuración de Bootloader ESP32: Guía Completa
El bootloader es el primer programa que se ejecuta en un ESP32. En esta guía aprenderás cómo configurarlo, personalizarlo y resolver problemas comunes.
¿Qué es un Bootloader?
El bootloader es un pequeño programa que:
- Se ejecuta inmediatamente después del reset
- Inicializa el hardware básico
- Decide qué aplicación cargar
- Maneja actualizaciones OTA
- Proporciona recuperación de errores
Arquitectura del ESP32
Mapa de Memoria Flash
Dirección | Tamaño | Descripción
-------------|--------|------------------
0x1000 | 28KB | Bootloader
0x8000 | 4KB | Tabla de particiones
0x9000 | 16KB | NVS (configuración)
0xD000 | 8KB | OTA data
0x10000 | 1MB | Aplicación principal
0x110000 | 1MB | OTA app (opcional)
0x210000 | 1MB | SPIFFS/LittleFS
Configuración con ESP-IDF
1. Configuración Básica
# Clonar ESP-IDF
git clone https://github.com/espressif/esp-idf.git
cd esp-idf
git checkout release/v5.1
./install.sh
# Configurar environment
. ./export.sh
# Crear proyecto
idf.py create-project bootloader_config
cd bootloader_config
2. Configuración de Particiones
Crear partitions.csv:
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x4000,
otadata, data, ota, 0xd000, 0x2000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
ota_0, app, ota_0, 0x110000, 1M,
ota_1, app, ota_1, 0x210000, 1M,
storage, data, spiffs, 0x310000, 0xF0000,
3. Configuración del Bootloader (sdkconfig)
# Bootloader config
CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y
CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=y
CONFIG_BOOTLOADER_WDT_ENABLE=y
CONFIG_BOOTLOADER_WDT_TIME_MS=9000
# Security features
CONFIG_SECURE_BOOT_ENABLED=n
CONFIG_SECURE_FLASH_ENC_ENABLED=n
# OTA config
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
CONFIG_BOOTLOADER_FACTORY_RESET=y
CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=512
# Partition table
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
Bootloader Personalizado
1. Estructura del Proyecto
proyecto/
├── main/
│ ├── main.c
│ └── CMakeLists.txt
├── bootloader_components/
│ └── my_bootloader/
│ ├── bootloader_start.c
│ └── CMakeLists.txt
├── partitions.csv
├── sdkconfig
└── CMakeLists.txt
2. Bootloader Personalizado
// bootloader_components/my_bootloader/bootloader_start.c
#include "esp_log.h"
#include "bootloader_init.h"
#include "bootloader_utility.h"
#include "bootloader_common.h"
static const char *TAG = "MY_BOOT";
void bootloader_main(void)
{
ESP_LOGI(TAG, "Custom Bootloader Starting...");
// Inicialización básica del hardware
if (bootloader_init() != ESP_OK) {
ESP_LOGE(TAG, "Failed to init bootloader");
return;
}
// Verificar GPIO para factory reset
if (bootloader_common_gpio_hold_reset_disable() == true) {
ESP_LOGW(TAG, "GPIO0 held - Factory reset triggered");
const esp_partition_t *factory_partition =
esp_partition_find_first(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_APP_FACTORY,
NULL);
if (factory_partition != NULL) {
bootloader_utility_load_image(factory_partition);
}
}
// LED de status durante boot
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << 2),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = 0,
.pull_down_en = 0,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
gpio_set_level(2, 1); // LED ON
// Seleccionar y cargar aplicación
const esp_partition_t *load_partition =
bootloader_utility_get_selected_boot_partition();
if (load_partition != NULL) {
ESP_LOGI(TAG, "Loading app from partition: %s", load_partition->label);
bootloader_utility_load_image(load_partition);
} else {
ESP_LOGE(TAG, "No valid app partition found");
}
}
3. CMakeLists.txt del Bootloader
# bootloader_components/my_bootloader/CMakeLists.txt
idf_component_register(
SRCS "bootloader_start.c"
INCLUDE_DIRS "."
REQUIRES bootloader_support log
)
Configuración OTA
1. Aplicación Principal con OTA
// main/main.c
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_http_client.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
static const char *TAG = "OTA_APP";
void check_ota_partition(void)
{
const esp_partition_t *running = esp_ota_get_running_partition();
esp_ota_img_states_t ota_state;
if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
ESP_LOGI(TAG, "First boot after OTA. Verifying image...");
// Aquí puedes agregar verificaciones adicionales
bool image_ok = true; // Tu lógica de verificación
if (image_ok) {
ESP_LOGI(TAG, "Image verified successfully");
esp_ota_mark_app_valid_cancel_rollback();
} else {
ESP_LOGE(TAG, "Image verification failed");
esp_restart();
}
}
}
}
void app_main(void)
{
// Inicializar 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);
// Verificar estado OTA
check_ota_partition();
ESP_LOGI(TAG, "Application started successfully");
// Tu aplicación principal aquí
while (1) {
ESP_LOGI(TAG, "App running...");
vTaskDelay(pdMS_TO_TICKS(10000));
}
}
Factory Reset
1. Configuración GPIO
// bootloader_components/my_bootloader/factory_reset.c
#include "bootloader_common.h"
#include "soc/gpio_reg.h"
#include "soc/rtc_io_reg.h"
#define FACTORY_RESET_PIN 0
bool check_factory_reset(void)
{
// Configurar GPIO0 como input con pull-up
gpio_pad_select_gpio(FACTORY_RESET_PIN);
gpio_set_direction(FACTORY_RESET_PIN, GPIO_MODE_INPUT);
gpio_pullup_en(FACTORY_RESET_PIN);
// Esperar estabilización
bootloader_common_delay_ms(100);
// Leer estado del pin
int pin_state = gpio_get_level(FACTORY_RESET_PIN);
if (pin_state == 0) {
ESP_LOGI(TAG, "Factory reset pin LOW - checking duration");
// Verificar que se mantenga presionado por 3 segundos
int count = 0;
for (int i = 0; i < 30; i++) {
bootloader_common_delay_ms(100);
if (gpio_get_level(FACTORY_RESET_PIN) == 0) {
count++;
} else {
break;
}
}
return (count >= 30); // 3 segundos
}
return false;
}
Debugging del Bootloader
1. Logs Detallados
// En bootloader_start.c
#define BOOTLOADER_BUILD_TIME __DATE__ " " __TIME__
void print_bootloader_info(void)
{
ESP_LOGI(TAG, "====== Custom Bootloader ======");
ESP_LOGI(TAG, "Build time: %s", BOOTLOADER_BUILD_TIME);
ESP_LOGI(TAG, "ESP-IDF version: %s", esp_get_idf_version());
ESP_LOGI(TAG, "Chip revision: %d", esp_chip_info.revision);
ESP_LOGI(TAG, "Flash size: %dMB", spi_flash_get_chip_size() / (1024 * 1024));
// Información de particiones
const esp_partition_t *factory = esp_partition_find_first(
ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);
if (factory) {
ESP_LOGI(TAG, "Factory partition: 0x%x, size: %d",
factory->address, factory->size);
}
}
2. Monitor Serie
# Monitoring continuo
idf.py monitor
# Con filtros específicos
idf.py monitor --print_filter="MY_BOOT:I"
# Monitor con timestamps
idf.py monitor --timestamps
Seguridad del Bootloader
1. Secure Boot
# sdkconfig
CONFIG_SECURE_BOOT_ENABLED=y
CONFIG_SECURE_BOOTLOADER_MODE_ONE_TIME_FLASH=y
CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES=y
2. Flash Encryption
# sdkconfig
CONFIG_SECURE_FLASH_ENC_ENABLED=y
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT=y
Herramientas de Desarrollo
1. Flashing Manual
# Flash bootloader personalizado
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 \
write_flash 0x1000 build/bootloader/bootloader.bin
# Flash tabla de particiones
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 \
write_flash 0x8000 build/partition_table/partition-table.bin
# Flash aplicación
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 \
write_flash 0x10000 build/my_app.bin
2. Verificación de Particiones
# Leer información de particiones
esptool.py --port /dev/ttyUSB0 read_flash 0x8000 0x1000 partition_table.bin
python $IDF_PATH/components/partition_table/gen_esp32part.py partition_table.bin
Troubleshooting
Problemas Comunes
- Boot Loop Infinito
# Verificar logs
idf.py monitor
# Factory reset forzado
esptool.py erase_flash
- Partition Table Corrupta
# Re-flash partition table
idf.py partition-table-flash
- OTA Rollback
// En la aplicación
esp_ota_mark_app_invalid_rollback_and_reboot();
Conclusión
Un bootloader bien configurado es crucial para:
- ✅ Recuperación: Factory reset y rollback automático
- ✅ OTA Updates: Actualizaciones seguras remotas
- ✅ Debugging: Logs detallados y troubleshooting
- ✅ Seguridad: Secure boot y flash encryption
- ✅ Flexibilidad: Múltiples particiones de aplicación
Próximos Pasos
- Implementar bootloader con menú interactivo
- Agregar verificación de integridad CRC32
- Configurar dual-boot con selección automática
- Implementar bootloader para ESP32-S3/C3
¿Necesitas ayuda con algún aspecto específico del bootloader ESP32? ¡Déjame un comentario!