From 22181580783e3cb91650970ae5fd99025ffcf5fa Mon Sep 17 00:00:00 2001 From: Kyunghwan Kwon Date: Wed, 3 Jan 2024 11:31:11 +0900 Subject: [PATCH] Add libmcu and unit test framework (#12) --- .github/codechecker.baseline | 3 - .github/memsize.baseline | 2 +- .github/workflows/ci.yml | 3 +- .gitmodules | 3 + CMakeLists.txt | 7 +- Makefile | 8 +- external/libmcu | 1 + include/metrics.def | 13 +++ main/CMakeLists.txt | 6 -- ports/{esp_idf => esp-idf}/build.cmake | 33 ++++++- ports/{esp_idf => esp-idf}/partitions.csv | 0 ports/{esp_idf => esp-idf}/sdkconfig.defaults | 4 +- ports/esp-idf/start.c | 27 ++++++ projects/external.cmake | 8 ++ projects/sources.cmake | 24 ++++++ projects/version.cmake | 30 +++++++ src/main.c | 86 ++++++++++--------- tests/Makefile | 63 ++++++++++++++ tests/runners/MakefileRunner | 68 +++++++++++++++ tests/src/test_all.cpp | 7 ++ 20 files changed, 336 insertions(+), 60 deletions(-) create mode 100644 .gitmodules create mode 160000 external/libmcu create mode 100644 include/metrics.def delete mode 100644 main/CMakeLists.txt rename ports/{esp_idf => esp-idf}/build.cmake (70%) rename ports/{esp_idf => esp-idf}/partitions.csv (100%) rename ports/{esp_idf => esp-idf}/sdkconfig.defaults (54%) create mode 100644 ports/esp-idf/start.c create mode 100644 projects/external.cmake create mode 100644 projects/sources.cmake create mode 100644 projects/version.cmake create mode 100644 tests/Makefile create mode 100644 tests/runners/MakefileRunner create mode 100644 tests/src/test_all.cpp diff --git a/.github/codechecker.baseline b/.github/codechecker.baseline index c5b8591..e69de29 100644 --- a/.github/codechecker.baseline +++ b/.github/codechecker.baseline @@ -1,3 +0,0 @@ -1c8a0938a69c1253735ac9878d0e0c5b -36764e646064a94b9467eb13219bf56d -afc0316ee5034294725332859b5e5a8d \ No newline at end of file diff --git a/.github/memsize.baseline b/.github/memsize.baseline index fddea5f..ca0d296 100644 --- a/.github/memsize.baseline +++ b/.github/memsize.baseline @@ -1,2 +1,2 @@ text data bss dec hex filename - 159173 46760 367669 573602 8c0a2 build/template.elf + 177729 50704 371141 599574 92616 build/template.elf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 232f670..a8e33f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,7 @@ jobs: run: | apt-get update && apt-get -y install cppcheck git config --system --add safe.directory '*' + git fetch echo "CODECHECKER_RESULT=$(echo .github/codechecker_result.txt)" >> $GITHUB_ENV echo "CODECHECKER_BASE=$(echo .github/codechecker.baseline)" >> $GITHUB_ENV echo "CODECHECKER_BASE_TMP=$(echo .github/codechecker_tmp.baseline)" >> $GITHUB_ENV @@ -38,7 +39,7 @@ jobs: - name: Process Memory Usage continue-on-error: true run: | - git checkout main $MEMSEG_BASE || cp $MEMSEG_BASE.template $MEMSEG_BASE + git checkout origin/main $MEMSEG_BASE || cp $MEMSEG_BASE.template $MEMSEG_BASE . $IDF_PATH/export.sh xtensa-esp32s3-elf-size build/*.elf > $MEMSEG_BASE_TMP echo -n "\`\`\`\n " > $MEMSEG_RESULT diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c69c68f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/libmcu"] + path = external/libmcu + url = https://github.com/libmcu/libmcu.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f228aa..620563c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,5 +13,10 @@ set(CMAKE_TOOLCHAIN_FILE $ENV{IDF_PATH}/tools/cmake/toolchain-${IDF_TARGET}.cmak project(template) +include(${CMAKE_SOURCE_DIR}/projects/version.cmake) +include(${CMAKE_SOURCE_DIR}/projects/sources.cmake) +include(${CMAKE_SOURCE_DIR}/projects/external.cmake) + include($ENV{IDF_PATH}/tools/cmake/idf.cmake) -include(${CMAKE_SOURCE_DIR}/ports/esp_idf/build.cmake) + +include(${CMAKE_SOURCE_DIR}/ports/esp-idf/build.cmake) diff --git a/Makefile b/Makefile index aac99bd..b440347 100644 --- a/Makefile +++ b/Makefile @@ -20,9 +20,9 @@ include version.mk all: build size +.PHONY: build build: - cmake -GNinja -B build - cmake --build build + idf.py build .PHONY: confirm confirm: @@ -55,13 +55,15 @@ clean: confirm size: $(Q)xtensa-esp32s3-elf-size $(BUILDIR)/$(PROJECT).elf +PORT ?= /dev/tty.usbmodem1101 .PHONY: flash -flash: $(BUILDIR)/esp32s3.bin +flash: $(BUILDIR)/$(PROJECT).bin $(Q)python $(IDF_PATH)/components/esptool_py/esptool/esptool.py \ --chip esp32s3 -p $(PORT) -b 921600 \ --before=default_reset --after=no_reset --no-stub \ write_flash \ --flash_mode dio --flash_freq 80m --flash_size keep \ + 0x0 $(BUILDIR)/bootloader/bootloader.bin \ 0x20000 $< \ 0xe000 $(BUILDIR)/partition_table/partition-table.bin \ 0x1d000 $(BUILDIR)/ota_data_initial.bin \ diff --git a/external/libmcu b/external/libmcu new file mode 160000 index 0000000..3038d5b --- /dev/null +++ b/external/libmcu @@ -0,0 +1 @@ +Subproject commit 3038d5b91f2ed705693763f95c682e049048c5e3 diff --git a/include/metrics.def b/include/metrics.def new file mode 100644 index 0000000..853f968 --- /dev/null +++ b/include/metrics.def @@ -0,0 +1,13 @@ +METRICS_DEFINE(HeartbeatInterval) +METRICS_DEFINE(UnixTime) +METRICS_DEFINE(MonotonicTime) +METRICS_DEFINE(ResetCount) +METRICS_DEFINE(ResetReason) +METRICS_DEFINE(Assertions) +METRICS_DEFINE(FaultExceptions) +METRICS_DEFINE(OOM) +METRICS_DEFINE(CPULoad) +METRICS_DEFINE(HeapLowWatermark) +METRICS_DEFINE(MainStackHighWatermark) +METRICS_DEFINE(HeapAllocFailure) +METRICS_DEFINE(BootingTime) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt deleted file mode 100644 index d2c8884..0000000 --- a/main/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -idf_component_register( - SRCS - "main.c" - INCLUDE_DIRS - "" -) diff --git a/ports/esp_idf/build.cmake b/ports/esp-idf/build.cmake similarity index 70% rename from ports/esp_idf/build.cmake rename to ports/esp-idf/build.cmake index 42d5cb3..7c471f5 100644 --- a/ports/esp_idf/build.cmake +++ b/ports/esp-idf/build.cmake @@ -34,17 +34,44 @@ set(build_component_paths_json "[]") configure_file("${IDF_PATH}/tools/cmake/project_description.json.in" "${CMAKE_CURRENT_BINARY_DIR}/project_description.json") +AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_LIST_DIR} PORT_SRCS) + +target_include_directories(libmcu PUBLIC + ${CMAKE_SOURCE_DIR}/external/libmcu/modules/common/include/libmcu/posix) +target_compile_definitions(libmcu PRIVATE timer_start=libmcu_timer_start) + +set(LIBMCU_ROOT ${PROJECT_SOURCE_DIR}/external/libmcu) +if ($ENV{IDF_VERSION} VERSION_LESS "5.1.0") + list(APPEND PORT_SRCS ${LIBMCU_ROOT}/ports/freertos/semaphore.c) +endif() + add_executable(${PROJECT_EXECUTABLE} - ${CMAKE_SOURCE_DIR}/src/main.c + ${APP_SRCS} + ${PORT_SRCS} + + ${LIBMCU_ROOT}/ports/esp-idf/board.c + ${LIBMCU_ROOT}/ports/esp-idf/actor.c + ${LIBMCU_ROOT}/ports/esp-idf/pthread.c + ${LIBMCU_ROOT}/ports/esp-idf/wifi.c + ${LIBMCU_ROOT}/ports/esp-idf/metrics.c + ${LIBMCU_ROOT}/ports/esp-idf/nvs_kvstore.c + ${LIBMCU_ROOT}/ports/freertos/timext.c + ${LIBMCU_ROOT}/ports/freertos/hooks.c + ${LIBMCU_ROOT}/ports/posix/logging.c + ${LIBMCU_ROOT}/ports/posix/button.c ) target_compile_definitions(${PROJECT_EXECUTABLE} PRIVATE + ${APP_DEFS} + ESP_PLATFORM=1 xPortIsInsideInterrupt=xPortInIsrContext ) target_include_directories(${PROJECT_EXECUTABLE} PRIVATE + ${APP_INCS} + $ENV{IDF_PATH}/components/freertos/FreeRTOS-Kernel/include/freertos $ENV{IDF_PATH}/components/freertos/include/freertos ${CMAKE_CURRENT_LIST_DIR} @@ -60,6 +87,10 @@ target_link_libraries(${PROJECT_EXECUTABLE} idf::esp_http_client idf::esp_https_ota idf::app_update + idf::esp_timer + idf::esp_wifi + + libmcu -Wl,--cref -Wl,--Map=\"${mapfile}\" diff --git a/ports/esp_idf/partitions.csv b/ports/esp-idf/partitions.csv similarity index 100% rename from ports/esp_idf/partitions.csv rename to ports/esp-idf/partitions.csv diff --git a/ports/esp_idf/sdkconfig.defaults b/ports/esp-idf/sdkconfig.defaults similarity index 54% rename from ports/esp_idf/sdkconfig.defaults rename to ports/esp-idf/sdkconfig.defaults index e043cb0..d1ce696 100644 --- a/ports/esp_idf/sdkconfig.defaults +++ b/ports/esp-idf/sdkconfig.defaults @@ -1,6 +1,6 @@ CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="ports/esp_idf/partitions.csv" -CONFIG_PARTITION_TABLE_FILENAME="ports/esp_idf/partitions.csv" +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="ports/esp-idf/partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="ports/esp-idf/partitions.csv" CONFIG_PARTITION_TABLE_OFFSET=0xE000 CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y diff --git a/ports/esp-idf/start.c b/ports/esp-idf/start.c new file mode 100644 index 0000000..14c0fd0 --- /dev/null +++ b/ports/esp-idf/start.c @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2023 Kyunghwan Kwon + * + * SPDX-License-Identifier: MIT + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "esp_event.h" +#include "esp_system.h" + +extern int main(void); +extern void app_main(void); + +static void esp_init(void) +{ + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); +} + +void app_main(void) +{ + esp_init(); + main(); +} diff --git a/projects/external.cmake b/projects/external.cmake new file mode 100644 index 0000000..7a24a01 --- /dev/null +++ b/projects/external.cmake @@ -0,0 +1,8 @@ +add_subdirectory(external/libmcu) + +target_compile_definitions(libmcu PUBLIC + _POSIX_THREADS + _POSIX_C_SOURCE=200809L + LIBMCU_NOINIT=__attribute__\(\(section\(\".rtc.data.libmcu\"\)\)\) + METRICS_USER_DEFINES=\"${PROJECT_SOURCE_DIR}/include/metrics.def\" +) diff --git a/projects/sources.cmake b/projects/sources.cmake new file mode 100644 index 0000000..e942a99 --- /dev/null +++ b/projects/sources.cmake @@ -0,0 +1,24 @@ +set(fpl-src-dirs src) +foreach(dir ${fpl-src-dirs}) + file(GLOB_RECURSE fpl_${dir}_SRCS RELATIVE ${CMAKE_SOURCE_DIR} ${dir}/*.c) + file(GLOB_RECURSE fpl_${dir}_CPP_SRCS RELATIVE ${CMAKE_SOURCE_DIR} ${dir}/*.cpp) + list(APPEND FPL_SRCS ${fpl_${dir}_SRCS} ${fpl_${dir}_CPP_SRCS}) +endforeach() + +set(APP_SRCS + ${CMAKE_SOURCE_DIR}/src/main.c +) +set(APP_INCS + ${CMAKE_SOURCE_DIR}/include +) +set(APP_DEFS + BUILD_DATE=${BUILD_DATE} + VERSION_MAJOR=${VERSION_MAJOR} + VERSION_MINOR=${VERSION_MINOR} + VERSION_PATCH=${VERSION_PATCH} + VERSION_TAG=${VERSION_TAG} + VERSION=${VERSION} + + _POSIX_THREADS + _POSIX_C_SOURCE=200809L +) diff --git a/projects/version.cmake b/projects/version.cmake new file mode 100644 index 0000000..6c16856 --- /dev/null +++ b/projects/version.cmake @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: Apache-2.0 + +execute_process( + COMMAND git describe --long --tags --dirty --always + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +string(REPLACE "-" ";" VERSION_STR ${VERSION}) +list(GET VERSION_STR 0 VERSION_NUMBER) +string(REPLACE "v" "" VERSION_NUMBER_LIST ${VERSION_NUMBER}) +string(REPLACE "." ";" VERSION_NUMBER_LIST ${VERSION_NUMBER_LIST}) +list(GET VERSION_NUMBER_LIST 0 VERSION_MAJOR) + +list(LENGTH VERSION_NUMBER_LIST VERSION_NUMBER_LIST_COUNT) + +if (${VERSION_NUMBER_LIST_COUNT} GREATER_EQUAL 2) +list(GET VERSION_NUMBER_LIST 1 VERSION_MINOR) +list(GET VERSION_STR 1 VERSION_PATCH) +endif() + +if (${VERSION_NUMBER_LIST_COUNT} GREATER_EQUAL 3) + list(GET VERSION_NUMBER_LIST 2 BUILD_NUMBER_ORG) + MATH(EXPR VERSION_PATCH "${BUILD_NUMBER_ORG} + ${VERSION_PATCH}") +endif() + +set(VERSION_TAG "v${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") + +string(TIMESTAMP BUILD_DATE "\"%Y-%m-%dT%H:%M:%SZ\"" UTC) diff --git a/src/main.c b/src/main.c index a282898..db0dc1f 100644 --- a/src/main.c +++ b/src/main.c @@ -1,49 +1,51 @@ /* - * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023 Kyunghwan Kwon * - * SPDX-License-Identifier: CC0-1.0 + * SPDX-License-Identifier: MIT */ #include -#include -#include "sdkconfig.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_chip_info.h" -#include "esp_flash.h" - -void app_main(void) + +#include "libmcu/board.h" +#include "libmcu/logging.h" + +static size_t logging_stdout_writer(const void *data, size_t size) +{ + unused(size); + static char buf[LOGGING_MESSAGE_MAXLEN]; + size_t len = logging_stringify(buf, sizeof(buf)-1, data); + + buf[len++] = '\n'; + buf[len] = '\0'; + + const size_t rc = fwrite(buf, len, 1, stdout); + + return rc == 0? len : 0; +} + +static void logging_stdout_backend_init(void) { - printf("Hello world!\n"); - - /* Print chip information */ - esp_chip_info_t chip_info; - uint32_t flash_size; - esp_chip_info(&chip_info); - printf("This is %s chip with %d CPU core(s), WiFi%s%s, ", - CONFIG_IDF_TARGET, - chip_info.cores, - (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "", - (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : ""); - - unsigned major_rev = chip_info.revision / 100; - unsigned minor_rev = chip_info.revision % 100; - printf("silicon revision v%d.%d, ", major_rev, minor_rev); - if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) { - printf("Get flash size failed"); - return; - } - - printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024), - (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); - - printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size()); - - for (int i = 10; i >= 0; i--) { - printf("Restarting in %d seconds...\n", i); - vTaskDelay(1000 / portTICK_PERIOD_MS); - } - printf("Restarting now.\n"); - fflush(stdout); - esp_restart(); + static struct logging_backend log_console = { + .write = logging_stdout_writer, + }; + + logging_add_backend(&log_console); +} + +int main(void) +{ + board_init(); /* should be called very first. */ + logging_init(board_get_time_since_boot_ms); + + logging_stdout_backend_init(); + + const board_reboot_reason_t reboot_reason = board_get_reboot_reason(); + + info("[%s] %s %s", board_get_reboot_reason_string(reboot_reason), + board_get_serial_number_string(), + board_get_version_string()); + + while (1) { + /* hang */ + } } diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..4a1c7fc --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,63 @@ +export CPPUTEST_HOME = cpputest +export TEST_BUILDIR ?= build + +TESTS := $(shell find runners -type f -regex ".*\.mk") + +.PHONY: all test compile gcov debug flags +all: test +test: BUILD_RULE=all +test: $(TESTS) +compile: BUILD_RULE=start +compile: $(TESTS) +gcov: BUILD_RULE=gcov +gcov: $(TESTS) +debug: BUILD_RULE=debug +debug: $(TESTS) +flags: BUILD_RULE=flags +flags: $(TESTS) + +.PHONY: $(TESTS) +$(TESTS): + $(MAKE) -f $@ $(BUILD_RULE) + +COVERAGE_FILE = $(TEST_BUILDIR)/coverage.info +.PHONY: $(COVERAGE_FILE) $(COVERAGE_FILE).init $(COVERAGE_FILE).run +$(COVERAGE_FILE).init: $(TEST_BUILDIR) + @lcov -o $@ \ + --base-directory . --directory . -c -i +$(COVERAGE_FILE).run: + @lcov -o $@ \ + --base-directory . --directory . -c +$(COVERAGE_FILE): $(COVERAGE_FILE).init $(COVERAGE_FILE).run + @lcov -o $@ \ + -a $(COVERAGE_FILE).init -a $(COVERAGE_FILE).run + @lcov -o $@ \ + --remove $@ \ + '*cpputest/*' \ + '/usr/*' \ + '*tests/*' \ + '*external/*' +$(TEST_BUILDIR)/test_coverage: $(COVERAGE_FILE) + @genhtml \ + -o $@ \ + -t "coverage" \ + --num-spaces 4 \ + $(COVERAGE_FILE) + +.PHONY: coverage +coverage: $(TEST_BUILDIR)/test_coverage + $(Q)open $(TEST_BUILDIR)/test_coverage/index.html +$(TEST_BUILDIR): $(TESTS) + +.PHONY: clean +clean: + $(Q)rm -rf $(TEST_BUILDIR) + +.PHONY: install reinstall +install: | $(CPPUTEST_HOME) + @cd $(CPPUTEST_HOME) && autoreconf -i && ./configure && make +reinstall: + @cd $(CPPUTEST_HOME) && make +$(CPPUTEST_HOME): + @git clone -b hack/malloc --single-branch \ + https://github.com/onkwon/cpputest.git $@ diff --git a/tests/runners/MakefileRunner b/tests/runners/MakefileRunner new file mode 100644 index 0000000..a0b2e56 --- /dev/null +++ b/tests/runners/MakefileRunner @@ -0,0 +1,68 @@ +ifndef SILENCE +SILENCE = @ +endif + +export TEST_TARGET = $(TEST_BUILDIR)/$(COMPONENT_NAME)_tests +export CPPUTEST_OBJS_DIR = $(TEST_BUILDIR)/objs +export CPPUTEST_LIB_DIR = $(TEST_BUILDIR)/lib + +export CPPUTEST_USE_EXTENSIONS = Y +export CPPUTEST_USE_MEM_LEAK_DETECTION = Y +export CPPUTEST_USE_GCOV = Y +export GCOV_ARGS = -abcfpu +export CPPUTEST_EXE_FLAGS = "-c" # colorize output + +export CPPUTEST_WARNINGFLAGS = \ + -Wall \ + -Wextra \ + -Wformat=2 \ + -Wmissing-prototypes \ + -Wstrict-prototypes \ + -Wmissing-declarations \ + -Wcast-align \ + -Wpointer-arith \ + -Wbad-function-cast \ + -Wnested-externs \ + -Wcast-qual \ + -Wmissing-format-attribute \ + -Wmissing-include-dirs \ + -Wformat-nonliteral \ + -Wdouble-promotion \ + -Wfloat-equal \ + -Winline \ + -Wundef \ + -Wunused-macros \ + -Wshadow \ + -Wwrite-strings \ + -Waggregate-return \ + -Wconversion \ + -Wstrict-overflow=5 \ + -Werror \ + \ + -Wswitch-default \ + -Wswitch-enum \ + -Wno-long-long \ + -Wno-missing-braces \ + -Wno-missing-field-initializers \ + -Wno-packed \ + -Wno-unused-parameter \ + \ + -Wno-error=switch-enum \ + -Wno-error=aggregate-return \ + +#-Wredundant-decls -Wswitch-enum + +ifeq ($(shell uname), Darwin) +CPPUTEST_WARNINGFLAGS += \ + -Wno-error=zero-as-null-pointer-constant \ + -Wno-error=poison-system-directories \ + -Wno-error=covered-switch-default \ + -Wno-error=format-nonliteral \ + -Wno-error=pedantic \ + -Wno-error=suggest-override \ + -Wno-error=suggest-destructor-override +else +#TARGET_PLATFORM ?= $(shell gcc -dumpmachine) +endif + +include $(CPPUTEST_HOME)/build/MakefileWorker.mk diff --git a/tests/src/test_all.cpp b/tests/src/test_all.cpp new file mode 100644 index 0000000..d7db87b --- /dev/null +++ b/tests/src/test_all.cpp @@ -0,0 +1,7 @@ +#include "CppUTest/CommandLineTestRunner.h" + +int main(int argc, char **argv) +{ + return RUN_ALL_TESTS(argc, argv); + //return CommandLineTestRunner::RunAllTests(argc, argv); +}