forked from espressif/esp-idf
feat(uhci): Add uart dma ota example for uhci
This commit is contained in:
@@ -506,6 +506,14 @@ examples/peripherals/twai/twai_self_test:
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
|
||||
examples/peripherals/uart/uart_dma_ota:
|
||||
disable:
|
||||
- if: SOC_UHCI_SUPPORTED != 1
|
||||
disable_test:
|
||||
- if: IDF_TARGET in ["esp32p4"]
|
||||
temporary: true
|
||||
reason: Lack runners
|
||||
|
||||
examples/peripherals/uart/uart_echo_rs485:
|
||||
enable:
|
||||
- if: INCLUDE_DEFAULT == 1
|
||||
|
6
examples/peripherals/uart/uart_dma_ota/CMakeLists.txt
Normal file
6
examples/peripherals/uart/uart_dma_ota/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(uart_dma_ota)
|
113
examples/peripherals/uart/uart_dma_ota/README.md
Normal file
113
examples/peripherals/uart/uart_dma_ota/README.md
Normal file
@@ -0,0 +1,113 @@
|
||||
| Supported Targets | ESP32-C3 | ESP32-C6 | ESP32-P4 | ESP32-S3 |
|
||||
| ----------------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
# UART OTA Example
|
||||
|
||||
This example demonstrates how to use UART to perform an Over-the-Air (OTA) firmware update on an ESP32-based device. It transfers firmware data via UART using DMA and applies the update seamlessly.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
The example can be run on any development board, that is based on the Espressif SoC. The board shall be connected to a computer with a single USB cable for flashing and monitoring. The external interface should have 3.3V outputs. You may use e.g. 3.3V compatible USB-to-Serial dongle.
|
||||
|
||||
### Software Required
|
||||
|
||||
1. Partition Table:
|
||||
|
||||
The example requires a valid partition table with an OTA partition. Update your partitions.csv or equivalent partition table to include an OTA data partition. Below is an example of a partition table with OTA support:
|
||||
|
||||
```
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
ota_0, app, ota_0, 0x110000, 1M,
|
||||
ota_1, app, ota_1, 0x210000, 1M,
|
||||
```
|
||||
|
||||
You can also use `CONFIG_PARTITION_TABLE_TWO_OTA_LARGE` if you don't want to define a partition table by yourself.
|
||||
|
||||
2. Firmware Binary:
|
||||
|
||||
Prepare the firmware binary (.bin file) you want to flash via OTA. This binary should be compatible with the ESP32 platform and must match the partition table configuration.
|
||||
Example:
|
||||
|
||||
*1.* Build the firmware binary via:
|
||||
|
||||
```
|
||||
idf.py build
|
||||
```
|
||||
|
||||
*2.* Locate the binary file in the build/ directory (e.g., build/your_project.bin)
|
||||
|
||||
### Setup the Hardware
|
||||
|
||||
Connect the external serial interface to the board as follows.
|
||||
|
||||
```
|
||||
|
||||
+-------------------+ +-------------------+
|
||||
| ESP Chip | | External UART Pin |
|
||||
| Interface | | |
|
||||
+-------------------+ +-------------------+
|
||||
| |
|
||||
| |
|
||||
Receive Data (RxD): + GPIO9 <------------------------------> TxD +
|
||||
| |
|
||||
Ground: + GND <--------------------------------> GND +
|
||||
| |
|
||||
|
||||
```
|
||||
|
||||
### Configure the project
|
||||
|
||||
Use the command below to configure project using Kconfig menu as showed in the table above.
|
||||
The default Kconfig values can be changed such as: UART_BAUD_RATE, UART_PORT_NUM, UART_RX_IO (Refer to Kconfig file).
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
Then transmit the prepared bin to ESP-chips. Please note that the bin should be in one packet transaction which means there should not have any interval during the transaction in this example because it uses idle to judge whether the packet finishes or not.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (277) main_task: Started on CPU0
|
||||
I (287) main_task: Calling app_main()
|
||||
I (287) uhci-example: UHCI initialized
|
||||
I (447) uhci-example: OTA process started
|
||||
I (18047) uhci-example: Received size: 150032
|
||||
I (18047) uhci-example: Total received size: 150032
|
||||
I (18047) esp_image: segment 0: paddr=001d0020 vaddr=3c020020 size=06748h ( 26440) map
|
||||
I (18057) esp_image: segment 1: paddr=001d6770 vaddr=3fc8b400 size=0111ch ( 4380)
|
||||
I (18067) esp_image: segment 2: paddr=001d7894 vaddr=40380000 size=08784h ( 34692)
|
||||
I (18077) esp_image: segment 3: paddr=001e0020 vaddr=42000020 size=11d98h ( 73112) map
|
||||
....
|
||||
I (18147) uhci-example: OTA update successful. Rebooting...
|
||||
.... (the following part depends on your bin, this output depends on example/get-started/hello_world)
|
||||
I (268) sleep_gpio: Enable automatic switching of GPIO sleep configuration
|
||||
I (275) main_task: Started on CPU0
|
||||
I (275) main_task: Calling app_main()
|
||||
Hello world!
|
||||
This is esp32c3 chip with 1 CPU core(s), WiFi/BLE, silicon revision v0.4, 4MB external flash
|
||||
Minimum free heap size: 333524 bytes
|
||||
Restarting in 10 seconds...
|
||||
Restarting in 9 seconds...
|
||||
Restarting in 8 seconds...
|
||||
```
|
||||
|
||||
|
||||
|
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "uart_dma_ota_example_main.c"
|
||||
REQUIRES esp_driver_uart app_update
|
||||
INCLUDE_DIRS ".")
|
@@ -0,0 +1,21 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config UART_RX_IO
|
||||
int "UART RX GPIO Num"
|
||||
default 2
|
||||
help
|
||||
GPIO number for UART RX line.
|
||||
|
||||
config UART_PORT_NUM
|
||||
int "UART port number"
|
||||
default 1
|
||||
help
|
||||
The UART device attached to DMA.
|
||||
|
||||
config UART_BAUD_RATE
|
||||
int "uart baud rate"
|
||||
default 1000000
|
||||
help
|
||||
Uart baud rate.
|
||||
|
||||
endmenu
|
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/uart.h"
|
||||
#include "driver/uhci.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_check.h"
|
||||
|
||||
static const char *TAG = "uhci-example";
|
||||
|
||||
#define EXAMPLE_UART_NUM CONFIG_UART_PORT_NUM
|
||||
#define EXAMPLE_UART_BAUD_RATE CONFIG_UART_BAUD_RATE
|
||||
#define EXAMPLE_UART_RX_IO CONFIG_UART_RX_IO
|
||||
#define UART_DMA_OTA_BUFFER_SIZE (10 * 1024)
|
||||
|
||||
typedef enum {
|
||||
UHCI_EVT_PARTIAL_DATA,
|
||||
UHCI_EVT_EOF,
|
||||
} uhci_event_t;
|
||||
|
||||
typedef struct {
|
||||
QueueHandle_t uhci_queue;
|
||||
size_t receive_size;
|
||||
uint8_t *ota_data1;
|
||||
uint8_t *ota_data2;
|
||||
bool use_ota_data1;
|
||||
} ota_example_context_t;
|
||||
|
||||
static bool s_uhci_rx_event_cbs(uhci_controller_handle_t uhci_ctrl, const uhci_rx_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
ota_example_context_t *ctx = (ota_example_context_t *)user_ctx;
|
||||
BaseType_t xTaskWoken = 0;
|
||||
uhci_event_t evt = 0;
|
||||
|
||||
if (edata->flags.totally_received) {
|
||||
evt = UHCI_EVT_EOF;
|
||||
} else {
|
||||
evt = UHCI_EVT_PARTIAL_DATA;
|
||||
}
|
||||
|
||||
// Choose the buffer to store received data
|
||||
ctx->receive_size = edata->recv_size;
|
||||
if (ctx->use_ota_data1) {
|
||||
ctx->ota_data1 = edata->data;
|
||||
} else {
|
||||
ctx->ota_data2 = edata->data;
|
||||
}
|
||||
|
||||
// Toggle the buffer for the next receive
|
||||
ctx->use_ota_data1 = !ctx->use_ota_data1;
|
||||
|
||||
xQueueSendFromISR(ctx->uhci_queue, &evt, &xTaskWoken);
|
||||
return xTaskWoken;
|
||||
}
|
||||
|
||||
static void perform_ota_update(uhci_controller_handle_t uhci_ctrl, ota_example_context_t *ctx)
|
||||
{
|
||||
const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL);
|
||||
if (!ota_partition) {
|
||||
ESP_LOGE(TAG, "No OTA partition found");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_ota_handle_t ota_handle;
|
||||
ESP_ERROR_CHECK(esp_ota_begin(ota_partition, OTA_SIZE_UNKNOWN, &ota_handle));
|
||||
ESP_LOGI(TAG, "OTA process started");
|
||||
|
||||
uhci_event_t evt;
|
||||
uint32_t received_size = 0;
|
||||
uint8_t *pdata = heap_caps_calloc(1, UART_DMA_OTA_BUFFER_SIZE, MALLOC_CAP_DEFAULT);
|
||||
assert(pdata);
|
||||
ESP_ERROR_CHECK(uhci_receive(uhci_ctrl, pdata, UART_DMA_OTA_BUFFER_SIZE));
|
||||
while (1) {
|
||||
if (xQueueReceive(ctx->uhci_queue, &evt, portMAX_DELAY) == pdTRUE) {
|
||||
uint8_t *data_to_write = ctx->use_ota_data1 ? ctx->ota_data2 : ctx->ota_data1;
|
||||
ESP_ERROR_CHECK(esp_ota_write(ota_handle, data_to_write, ctx->receive_size));
|
||||
received_size += ctx->receive_size;
|
||||
if (evt == UHCI_EVT_EOF) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
free(pdata);
|
||||
|
||||
ESP_LOGI(TAG, "Total received size: %ld", received_size);
|
||||
ESP_ERROR_CHECK(esp_ota_end(ota_handle));
|
||||
ESP_ERROR_CHECK(esp_ota_set_boot_partition(ota_partition));
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = EXAMPLE_UART_BAUD_RATE,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(uart_param_config(EXAMPLE_UART_NUM, &uart_config));
|
||||
ESP_ERROR_CHECK(uart_set_pin(EXAMPLE_UART_NUM, -1, EXAMPLE_UART_RX_IO, -1, -1));
|
||||
|
||||
uhci_controller_config_t uhci_cfg = {
|
||||
.uart_port = EXAMPLE_UART_NUM,
|
||||
.tx_trans_queue_depth = 1,
|
||||
.max_receive_internal_mem = UART_DMA_OTA_BUFFER_SIZE,
|
||||
.max_transmit_size = UART_DMA_OTA_BUFFER_SIZE,
|
||||
.dma_burst_size = 32,
|
||||
.rx_eof_flags.idle_eof = 1, // receive finishes when rx line turns idle.
|
||||
};
|
||||
|
||||
uhci_controller_handle_t uhci_ctrl;
|
||||
ESP_ERROR_CHECK(uhci_new_controller(&uhci_cfg, &uhci_ctrl));
|
||||
|
||||
ESP_LOGI(TAG, "UHCI initialized, baud rate is %d, rx pin is %d", uart_config.baud_rate, EXAMPLE_UART_RX_IO);
|
||||
|
||||
ota_example_context_t *ctx = calloc(1, sizeof(ota_example_context_t));
|
||||
assert(ctx);
|
||||
|
||||
ctx->uhci_queue = xQueueCreate(2, sizeof(uhci_event_t));
|
||||
assert(ctx->uhci_queue);
|
||||
|
||||
ctx->use_ota_data1 = true; // Start with ota_data1
|
||||
|
||||
uhci_event_callbacks_t uhci_cbs = {
|
||||
.on_rx_trans_event = s_uhci_rx_event_cbs,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(uhci_register_event_callbacks(uhci_ctrl, &uhci_cbs, ctx));
|
||||
|
||||
perform_ota_update(uhci_ctrl, ctx);
|
||||
|
||||
ESP_ERROR_CHECK(uhci_del_controller(uhci_ctrl));
|
||||
|
||||
free(ctx);
|
||||
ESP_LOGI(TAG, "OTA update successful. Rebooting...");
|
||||
esp_restart();
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import serial
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
PACKET_SIZE = 10 * 1024 # 10 KB for upper computer
|
||||
FLASH_PORT = '/dev/serial_ports/ttyUSB-esp32'
|
||||
|
||||
|
||||
def send_file_via_uart(port: str, baud_rate: int, file_path: str, packet_size: int) -> None:
|
||||
try:
|
||||
with serial.Serial(port, baud_rate, timeout=1) as ser:
|
||||
print(f'Opened {port} at {baud_rate} baud')
|
||||
|
||||
file_size = os.path.getsize(file_path)
|
||||
print(f'File size: {file_size} bytes')
|
||||
|
||||
with open(file_path, 'rb') as file:
|
||||
bytes_sent = 0
|
||||
while bytes_sent < file_size:
|
||||
chunk = file.read(packet_size)
|
||||
if not chunk:
|
||||
break
|
||||
ser.write(chunk)
|
||||
bytes_sent += len(chunk)
|
||||
|
||||
print('File sent successfully!')
|
||||
|
||||
except Exception as e:
|
||||
print(f'Error: {e}')
|
||||
|
||||
|
||||
@pytest.mark.usb_serial_jtag
|
||||
@pytest.mark.parametrize(
|
||||
'port, flash_port, config',
|
||||
[
|
||||
pytest.param('/dev/serial_ports/ttyACM-esp32', FLASH_PORT, 'defaults'),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize('target', ['esp32c6', 'esp32c3', 'esp32s3'], indirect=['target'])
|
||||
def test_uart_dma_ota(dut: Dut) -> None:
|
||||
dut.expect_exact('uhci-example: OTA process started')
|
||||
# We OTA the same binary to another partition and switch to there.
|
||||
binary_path = os.path.join(dut.app.binary_path, 'uart_dma_ota.bin')
|
||||
assert os.path.exists(binary_path), f'OTA binary not found at {binary_path}'
|
||||
|
||||
buad_rate = dut.app.sdkconfig.get('UART_BAUD_RATE')
|
||||
send_file_via_uart(FLASH_PORT, buad_rate, binary_path, PACKET_SIZE)
|
||||
|
||||
dut.expect('OTA update successful. Rebooting', timeout=10)
|
||||
dut.expect('ESP-ROM:', timeout=10)
|
@@ -0,0 +1,2 @@
|
||||
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
|
||||
CONFIG_UART_PORT_NUM=0
|
@@ -0,0 +1,2 @@
|
||||
CONFIG_IDF_TARGET="esp32c3"
|
||||
CONFIG_UART_RX_IO=20
|
@@ -0,0 +1,2 @@
|
||||
CONFIG_IDF_TARGET="esp32c6"
|
||||
CONFIG_UART_RX_IO=17
|
@@ -0,0 +1,2 @@
|
||||
CONFIG_IDF_TARGET="esp32s3"
|
||||
CONFIG_UART_RX_IO=44
|
@@ -0,0 +1,3 @@
|
||||
# partition table layout, with a 4MB flash size
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
CONFIG_PARTITION_TABLE_TWO_OTA_LARGE=y
|
Reference in New Issue
Block a user