feat(ulp): add option for routing LP-printf to HP console

This commit is contained in:
Marius Vikhammer 2024-07-16 15:35:10 +08:00
parent 42bcb84013
commit 74a04c140c
38 changed files with 226 additions and 7 deletions

View File

@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/** ROM APIs
*/
PROVIDE ( esp_rom_output_putc = uart_tx_one_char );

View File

@ -106,6 +106,25 @@ menu "Ultra Low Power (ULP) Co-processor"
Disabling this option will reduce the LP core binary size by not
linking in panic handler functionality.
config ULP_HP_UART_CONSOLE_PRINT
depends on ULP_COPROC_TYPE_LP_CORE
bool
prompt "Route lp_core_printf to the console HP-UART"
help
Set this option to route lp_core_printf to the console HP-UART.
This allows you to easily view print outputs from the LP core, without
having to connect to the LP-UART. This option comes with the following
limitations:
1. There is no mutual exclusion between the HP-Core and the LP-Core accessing
the HP-UART, which means that if both cores are logging heavily the output
strings might get mangled together.
2. The HP-UART can only work while the HP-Core is running, which means that
if the HP-Core is in deep sleep, the LP-Core will not be able to print to the
console HP-UART.
Due to these limitations it is only recommended to use this option for easy debugging.
For more serious use-cases you should use the LP-UART.
endmenu
endmenu # Ultra Low Power (ULP) Co-processor

View File

@ -135,6 +135,8 @@ elseif(CONFIG_ULP_COPROC_TYPE_LP_CORE)
PRIVATE SHELL:-T ${IDF_PATH}/components/esp_rom/${IDF_TARGET}/ld/${IDF_TARGET}lp.rom.newlib.ld)
target_link_options(${ULP_APP_NAME}
PRIVATE SHELL:-T ${IDF_PATH}/components/esp_rom/${IDF_TARGET}/ld/${IDF_TARGET}lp.rom.version.ld)
target_link_options(${ULP_APP_NAME}
PRIVATE SHELL:-T ${IDF_PATH}/components/esp_rom/${IDF_TARGET}/ld/${IDF_TARGET}lp.rom.api.ld)
endif()
target_sources(${ULP_APP_NAME} PRIVATE ${ULP_S_SOURCES})

View File

@ -37,3 +37,10 @@ void lp_core_printf(const char* format, ...);
extern void ets_install_uart_printf(void);
#define lp_core_install_uart_print ets_install_uart_printf
#endif /* CONFIG_ULP_ROM_PRINT_ENABLE */
/**
* @brief Print a single character from the LP core
*
* @param c character to be printed
*/
void lp_core_print_char(char c);

View File

@ -6,25 +6,47 @@
#include <stdarg.h>
#include "sdkconfig.h"
#include "ulp_lp_core_uart.h"
#if !CONFIG_ULP_ROM_PRINT_ENABLE
#include "hal/uart_hal.h"
#include "esp_rom_uart.h"
#define LP_UART_PORT_NUM LP_UART_NUM_0
#define BINARY_SUPPORT 1
#define is_digit(c) ((c >= '0') && (c <= '9'))
#if CONFIG_ULP_HP_UART_CONSOLE_PRINT
void __attribute__((alias("hp_uart_send_char"))) lp_core_print_char(char c);
static void hp_uart_send_char(char t)
{
uart_dev_t *uart = (uart_dev_t *)UART_LL_GET_HW(CONFIG_ESP_CONSOLE_UART_NUM);
while (uart_ll_get_txfifo_len(uart) < 2) {
;
}
uart_ll_write_txfifo(uart, &t, 1);
}
#elif !CONFIG_ULP_ROM_PRINT_ENABLE
void __attribute__((alias("lp_uart_send_char"))) lp_core_print_char(char c);
static void lp_uart_send_char(char c)
{
int tx_len = 0;
int loop_cnt = 0;
/* Write one byte to LP UART. Break after few iterations if we are stuck for any reason. */
while (tx_len != 1 && loop_cnt < 1000) {
tx_len = lp_core_uart_tx_chars(LP_UART_PORT_NUM, (const void *)&c, 1);
loop_cnt++;
}
}
#else
void __attribute__((alias("lp_rom_send_char"))) lp_core_print_char(char c);
static void lp_rom_send_char(char c)
{
esp_rom_output_putc(c);
}
#endif // CONFIG_ULP_HP_UART_CONSOLE_PRINT
#if !CONFIG_ULP_ROM_PRINT_ENABLE
// Ported over ROM function _cvt()
static int lp_core_cvt(unsigned long long val, char *buf, long radix, char *digits)
@ -268,7 +290,7 @@ int lp_core_printf(const char* format, ...)
va_start(ap, format);
/* Pass the input string and the argument list to ets_vprintf() */
int ret = lp_core_ets_vprintf(lp_uart_send_char, format, ap);
int ret = lp_core_ets_vprintf(lp_core_print_char, format, ap);
va_end(ap);

View File

@ -1,18 +1,27 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#include "esp_rom_caps.h"
#include "rom/ets_sys.h"
#include "ulp_lp_core_utils.h"
#include "ulp_lp_core_lp_timer_shared.h"
#include "ulp_lp_core_memory_shared.h"
#include "ulp_lp_core_print.h"
extern void main();
/* Initialize lp core related system functions before calling user's main*/
void lp_core_startup()
{
#if CONFIG_ULP_HP_UART_CONSOLE_PRINT && ESP_ROM_HAS_LP_ROM
ets_install_putc1(lp_core_print_char);
#endif
ulp_lp_core_update_wakeup_cause();
main();

View File

@ -11,4 +11,4 @@ set(EXTRA_COMPONENT_DIRS
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(lp_core_test)
project(lp_core_basic_tests)

View File

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut

View File

@ -0,0 +1,14 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)
list(PREPEND SDKCONFIG_DEFAULTS "$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers" "sdkconfig.defaults")
set(EXTRA_COMPONENT_DIRS
"$ENV{IDF_PATH}/tools/unit-test-app/components"
)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(lp_core_hp_uart_test)

View File

@ -0,0 +1,3 @@
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-P4 |
| ----------------- | -------- | -------- | -------- |

View File

@ -0,0 +1,11 @@
set(app_sources "test_app_main.c" "test_lp_core.c")
set(lp_core_sources "lp_core/test_hello_main.c")
idf_component_register(SRCS ${app_sources}
INCLUDE_DIRS "lp_core"
REQUIRES ulp unity esp_timer test_utils
WHOLE_ARCHIVE)
set(lp_core_exp_dep_srcs ${app_sources})
ulp_embed_binary(lp_core_test_app "${lp_core_sources}" "${lp_core_exp_dep_srcs}")

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include "ulp_lp_core_print.h"
int main(void)
{
lp_core_printf("Hello, World!\n");
return 0;
}

View File

@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include "unity_test_runner.h"
#include "esp_heap_caps.h"
// Some resources are lazy allocated in the sleep code, the threshold is left for that case
#define TEST_MEMORY_LEAK_THRESHOLD (-500)
static size_t before_free_8bit;
static size_t before_free_32bit;
static void check_leak(size_t before_free, size_t after_free, const char *type)
{
ssize_t delta = after_free - before_free;
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
}
void setUp(void)
{
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
}
void tearDown(void)
{
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(before_free_8bit, after_free_8bit, "8BIT");
check_leak(before_free_32bit, after_free_32bit, "32BIT");
}
void app_main(void)
{
unity_run_menu();
}

View File

@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <inttypes.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "unity.h"
#include "soc/soc_caps.h"
#include "esp_rom_caps.h"
#include "lp_core_test_app.h"
#include "ulp_lp_core.h"
extern const uint8_t lp_core_main_bin_start[] asm("_binary_lp_core_test_app_bin_start");
extern const uint8_t lp_core_main_bin_end[] asm("_binary_lp_core_test_app_bin_end");
static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end)
{
TEST_ASSERT(ulp_lp_core_load_binary(firmware_start,
(firmware_end - firmware_start)) == ESP_OK);
TEST_ASSERT(ulp_lp_core_run(cfg) == ESP_OK);
}
TEST_CASE("lp-print can output to hp-uart", "[lp_core]")
{
/* Load ULP firmware and start the coprocessor */
ulp_lp_core_cfg_t cfg = {
.wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU,
};
load_and_start_lp_core_firmware(&cfg, lp_core_main_bin_start, lp_core_main_bin_end);
// Actual test output on UART is checked by pytest, not unity test-case
// We simply wait to allow the lp-core to run once
vTaskDelay(1000 / portTICK_PERIOD_MS);
}

View File

@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32c5
@pytest.mark.esp32c6
@pytest.mark.esp32p4
@pytest.mark.generic
def test_lp_core_hp_uart_print(dut: Dut) -> None:
dut.expect_exact('Press ENTER to see the list of tests')
dut.write('"lp-print can output to hp-uart"')
dut.expect_exact('Hello, World!')

View File

@ -0,0 +1,7 @@
CONFIG_ESP_TASK_WDT_INIT=n
CONFIG_ULP_COPROC_ENABLED=y
CONFIG_ULP_COPROC_TYPE_LP_CORE=y
CONFIG_ULP_COPROC_RESERVE_MEM=12000
CONFIG_ULP_PANIC_OUTPUT_ENABLE=y
CONFIG_ULP_HP_UART_CONSOLE_PRINT=y

View File

@ -212,6 +212,8 @@ When programming the LP-Core, it can sometimes be challenging to figure out why
* Use the LP-UART to print: the LP-Core has access to the LP-UART peripheral, which can be used for printing information independently of the main CPU sleep state. See :example:`system/ulp/lp_core/lp_uart/lp_uart_print` for an example of how to use this driver.
* Routing :cpp:func:`lp_core_printf` to the HP-Core console UART with :ref:`CONFIG_ULP_HP_UART_CONSOLE_PRINT`. This allows you to easily print LP-Core information to the already connected HP-Core console UART. The drawback of this approach is that it requires the main CPU to be awake and since there is no synchronization between the LP and HP cores, the output may be interleaved.
* Share program state through shared variables: as described in :ref:`ulp-lp-core-access-variables`, both the main CPU and the ULP core can easily access global variables in RTC memory. Writing state information to such a variable from the ULP and reading it from the main CPU can help you discern what is happening on the ULP core. The downside of this approach is that it requires the main CPU to be awake, which will not always be the case. Keeping the main CPU awake might even, in some cases, mask problems, as some issues may only occur when certain power domains are powered down.
* Panic handler: the LP-Core has a panic handler that can dump the state of the LP-Core registers by the LP-UART when an exception is detected. To enable the panic handler, set the :ref:`CONFIG_ULP_PANIC_OUTPUT_ENABLE` option to ``y``. This option can be kept disabled to reduce LP-RAM usage by the LP-Core application. To recover a backtrace from the panic dump, it is possible to use esp-idf-monitor_., e.g.: