Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
[submodule "dev/OpenOCD"]
path = dev/OpenOCD
url = https://github.com/STMicroelectronics/OpenOCD.git
[submodule "cdefmt"]
path = cdefmt_port/cdefmt
url = git@github.com:bjackson312006/cdefmt.git
61 changes: 61 additions & 0 deletions cdefmt_port/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
cmake_minimum_required(VERSION 3.22)

set(_CDEFMT_PORT_DIR ${CMAKE_CURRENT_LIST_DIR})
set(_CDEFMT_UPSTREAM_DIR ${_CDEFMT_PORT_DIR}/cdefmt)

if(NOT EXISTS ${_CDEFMT_UPSTREAM_DIR}/CMakeLists.txt)
message(FATAL_ERROR
"cdefmt_port: upstream cdefmt submodule not found at "
"${_CDEFMT_UPSTREAM_DIR}. Did you `git submodule update --init "
"--recursive`?")
endif()

if(NOT TARGET cdefmt)
add_subdirectory(
${_CDEFMT_UPSTREAM_DIR}
${CMAKE_BINARY_DIR}/cdefmt
EXCLUDE_FROM_ALL
)
endif()

# Linker script fragment that defines the `.cdefmt` metadata section and the
# `.note.gnu.build-id` section. It is passed as an additional `-T` argument;
# GNU ld merges multiple `-T` scripts, so this layers cleanly on top of the
# main linker script without replacing it.
set(_CDEFMT_LD_FRAGMENT ${_CDEFMT_PORT_DIR}/cdefmt_sections.ld)

add_library(embedded_base_cdefmt INTERFACE)

target_link_libraries(embedded_base_cdefmt INTERFACE cdefmt)

target_include_directories(embedded_base_cdefmt INTERFACE
${_CDEFMT_PORT_DIR}/include
)

# Treat cdefmt's header and its Boost.Preprocessor/VMD dependencies as system
# headers for anyone who links `embedded_base_cdefmt`.
foreach(_cdefmt_dep cdefmt Boost::preprocessor Boost::vmd)
if(TARGET ${_cdefmt_dep})
# Boost::* are ALIAS targets; set_property() rejects aliases, so resolve
# to the underlying real target before touching its properties.
get_target_property(_cdefmt_real ${_cdefmt_dep} ALIASED_TARGET)
if(NOT _cdefmt_real)
set(_cdefmt_real ${_cdefmt_dep})
endif()
get_target_property(_cdefmt_dep_includes
${_cdefmt_real} INTERFACE_INCLUDE_DIRECTORIES)
if(_cdefmt_dep_includes)
set_property(TARGET ${_cdefmt_real} APPEND PROPERTY
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES ${_cdefmt_dep_includes})
endif()
endif()
endforeach()

# cdefmt's decoder needs DWARF info to parse argument types.
target_compile_options(embedded_base_cdefmt INTERFACE -g)

# Link-time requirements
target_link_options(embedded_base_cdefmt INTERFACE
"LINKER:--build-id"
"-T${_CDEFMT_LD_FRAGMENT}"
)
76 changes: 76 additions & 0 deletions cdefmt_port/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# cdefmt_port

This is a wrapper layer to integrate the `cdefmt` into Embedded-Base. `cdefmt` is a library someone made that provides similar functionality to the `defmt` Rust crate. Link: https://github.com/RisinT96/cdefmt

## Setting Up cdefmt

To set up CDEFMT logging in an existing project, there are a few steps you need to follow.

First, make sure the project has Embedded-Base as a submodule, on a commit that has cdefmt_port included. You'll need to run `git submodule update --init --recursive` to make sure the cdefmt library is pulled.

Then, add this to the parent project's top-level `CMakeLists.txt`:

```cmake
add_subdirectory(Drivers/Embedded-Base/cdefmt_port)

target_link_libraries(${CMAKE_PROJECT_NAME}
embedded_base_cdefmt
# ... other libs ...
)
```

After that, add the cdefmt routing code to your project's `main.c`:

```c
#include <cdefmt/include/cdefmt.h>

/* Emits the static-inline `cdefmt_init()` function used during start-up to
* send the build-id init log. Must appear at file scope exactly once in the
* whole program. */
CDEFMT_GENERATE_INIT();

/**
* @brief Transport sink invoked by every CDEFMT_* macro.
*/
void cdefmt_log(const void *log, size_t size, enum cdefmt_level level)
{
(void)level; /* Runtime level filtering can be added here if desired. */

const uint64_t hdr = (uint64_t)size;

HAL_UART_Transmit(&huart2, (uint8_t *)&hdr, sizeof hdr, HAL_MAX_DELAY);
HAL_UART_Transmit(&huart2, (uint8_t *)log, (uint16_t)size, HAL_MAX_DELAY);
}
```
(Obviously, you should use whatever UART your project has available, rather than specifically `huart2`. Also, make sure nothing else is using this UART aside from cdefmt; if you also have printf() routed to this same UART or something, you'll get errors when trying to run `ner cdefmt`.)

Also, add `cdefmt_init()` to the `main()` function in your `main.c`, after your UART initialization code has ran.

# Usage

Now, you should be ready to use cdefmt. In any file where you want to log stuff, add `#include <cdefmt/include/cdefmt.h>` to the top. Then, you can use the following cdefmt macros:
```c
CDEFMT_ERROR("This is an error log.");
CDEFMT_WARNING("This is a warning log.");
CDEFMT_INFO("This is an info log.");
CDEFMT_DEBUG("This is a debug log.");
CDEFMT_VERBOSE("This is a verbose log.");
```

Here's an example of how formatting data works in these macros:
```c
CDEFMT_DEBUG("loop state: tick={} led_pin={:#x}, cool_float={}", tick, led_pin, cool_float);
// `tick` is a uint32_t, `led_pin` is a uint16_t that we want to display in hex, and `cool_float` is a float
```

That macro call produced this log when I ran the code and viewed logging output (via `ner cdefmt`):
```
[u_threads.c/vDefault():78] Debug > loop state: tick=17 led_pin=0x20, cool_float=64.08844
```
As shown above, the logs are automatically prepended with the filename, function name, line number, and logging level (e.g., Debug, Warning, Error, etc) corresponding to the macro call.

# Other Important Stuff

- Using cdefmt requires cargo, since the decoder will need to get built on your device.
- It may be helpful to run `ner cdefmt --rebuild` if you run into issues, since this will rebuild the decoder on your device.
- Again, you NEED to give cdefmt its own dedicated UART (or just disable printf()-to-UART routing) for `ner cdefmt` to work. If `ner cdefmt` encounters non-defmt prints when trying to decode, it will fail.
1 change: 1 addition & 0 deletions cdefmt_port/cdefmt
Submodule cdefmt added at e413a4
14 changes: 14 additions & 0 deletions cdefmt_port/cdefmt_sections.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* cdefmt linker script fragment.
*
*/

SECTIONS {
.cdefmt 0 (INFO) : {
KEEP(*(.cdefmt.init .cdefmt.init.*))
. = . + 8;
KEEP(*(.cdefmt .cdefmt.*))
}
}

PROVIDE(__cdefmt_build_id = ADDR(.note.gnu.build-id));
26 changes: 26 additions & 0 deletions cdefmt_port/include/config/cdefmt_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#ifndef CDEFMT_CONFIG_H
#define CDEFMT_CONFIG_H

/* ---------------------------------------------------------------------------
* Stack-allocated log buffer (default).
*
* Each CDEFMT_* call materializes a packed struct on the caller's stack of
* size: sizeof(log_id) + sum(sizeof(args)) + DYNAMIC_SIZE_MAX bytes. Threads
* that log must have enough stack headroom to cover this.
* ------------------------------------------------------------------------- */
#define CDEFMT_USE_STACK_LOG_BUFFER 1
#define CDEFMT_STACK_LOG_BUFFER_DYNAMIC_SIZE_MAX 64

/* ---------------------------------------------------------------------------
* Alternative buffer modes (disabled by default).
*
* If switching to STATIC, also provide CDEFMT_STATIC_LOG_BUFFER,
* CDEFMT_STATIC_LOG_BUFFER_SIZE, and a lock/unlock pair if multi-threaded.
*
* If switching to DYNAMIC, also provide CDEFMT_DYNAMIC_LOG_BUFFER_ALLOC and
* CDEFMT_DYNAMIC_LOG_BUFFER_FREE.
* ------------------------------------------------------------------------- */
#define CDEFMT_USE_STATIC_LOG_BUFFER 0
#define CDEFMT_USE_DYNAMIC_LOG_BUFFER 0

#endif /* CDEFMT_CONFIG_H */
18 changes: 18 additions & 0 deletions ner_environment/build_system/build_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
# custom modules for functinality that is too large to be included in this script directly
from .miniterm import main as miniterm
from .serial2 import main as serial2_start
from .cdefmt import main as cdefmt_main

# ==============================================================================
# Typer application setup
Expand Down Expand Up @@ -262,6 +263,23 @@ def serial2(

serial2_start(ls=ls, device=device, monitor=monitor, graph=graph, filter=filter)

# ==============================================================================
# CDEFMT command
# ==============================================================================

@app.command(help="Live-decode cdefmt binary log frames coming over UART. Requires the cdefmt submodule (Drivers/Embedded-Base/cdefmt) and `cargo` on PATH for the first-run decoder build.")
def cdefmt(device: str = typer.Option("", "--device", "-d",
help="Serial device to read from (e.g. /dev/ttyACM0). Auto-detected if omitted."),
elf: str = typer.Option(None, "--elf",
help="ELF file to decode against. Defaults to the most recently built build/*.elf."),
rebuild: bool = typer.Option(False, "--rebuild",
help="Force a rebuild of the host-side cdefmt decoder before running."),
ls: bool = typer.Option(False, "--list",
help="List candidate serial ports and ELF files, then exit."),
baud: int = typer.Option(115200, "--baud", "-b",
help="Serial baud rate (USB CDC ACM ignores it, but stty wants a value).")):
cdefmt_main(device=device, elf=elf, rebuild=rebuild, ls=ls, baud=baud)

# ==============================================================================
# Test command
# ==============================================================================
Expand Down
Loading