Skip to content

Latest commit

 

History

History
160 lines (117 loc) · 11.6 KB

File metadata and controls

160 lines (117 loc) · 11.6 KB

AGENTS.md

Guidance for AI coding agents (Claude Code, Cursor, Copilot, etc.) working in this repository.

What this project is

LwOW (Lightweight OneWire) is a portable C library implementing the 1-Wire master protocol. The exclusive target is embedded systems — typically MCUs in the kilobyte-RAM class, bare-metal or running an RTOS. Every design decision in the codebase serves that constraint, and any change you propose has to as well.

It is part of the Lw* family of embedded libraries by Tilen Majerle (LwESP, LwMEM, LwJSON, LwRB, ...).

The library's distinguishing design choice is that 1-Wire timing is generated by the host's UART peripheral rather than bit-banged GPIO:

  • 1-Wire bit timing aligns with UART byte timing — 9600 baud for the reset pulse, 115200 baud for data bits.
  • The hardware UART owns the timing, so the application can be preempted between bytes (interrupts, RTOS context switches, even DMA-driven transfers) without breaking the protocol.
  • A GPIO bit-bang fallback driver exists for systems where no UART is available; it trades CPU cost for portability.

The result is a 1-Wire master that fits comfortably in a few kB of flash, allocates no heap, runs on bare metal or under an RTOS, and lets the user choose between hand-rolled UART drivers and DMA-backed ones.

The DS18B20 temperature sensor is the only device driver shipped natively; other devices follow the same pattern.

Embedded-system constraints (read this first)

These are not stylistic preferences — violating them breaks the library for its actual users. When in doubt, choose the option that is smaller, more deterministic, or more portable.

  • Freestanding-friendly C11. No C++. The core only uses things you'd reasonably expect on an MCU toolchain (<stdint.h>, <stddef.h>, <string.h> for memcpy/memset). Don't pull in anything from <stdio.h>, <stdlib.h>, <math.h>, <time.h>, etc. in core/device code.
  • No dynamic allocation in the core. No malloc/calloc/realloc/free. Verified across lwow/src/lwow/, lwow/src/devices/, lwow/src/system/. The user supplies storage; the library borrows it via pointers.
  • No platform headers in core. Core code (lwow.c, lwow_device_ds18x20.c) must not include <windows.h>, stm32*.h, cmsis_os.h, FreeRTOS headers, etc. All platform contact lives behind lwow_ll_drv_t (UART) and the lwow_sys_* API (OS mutex). Reference port files under lwow/src/system/ are the only places those headers are allowed.
  • Be aware of stack and code size. Small fixed-size buffers on the stack are fine; large arrays, deep recursion, or anything proportional to "number of devices on the bus" is not. Prefer iteration; don't add convenience APIs that allocate stack scratch the user didn't ask for.
  • Predictable, interrupt-safe behaviour. UART RX/TX often runs from ISR or DMA-completion contexts in the user's port. Don't add long blocking loops, busy-waits, or non-reentrant globals to the core path.
  • No floating point in core. The library core deliberately uses integer math. The DS18x20 driver is the documented exception — lwow_ds18x20_read and lwow_ds18x20_read_raw expose float* for the converted temperature (lwow/src/include/lwow/devices/lwow_device_ds18x20.h:63-64). Don't introduce floats elsewhere.
  • Every tunable goes through lwow_opt.h. Each option is #ifndef LWOW_CFG_X / #define LWOW_CFG_X default so the user can override it from lwow_opts.h. Don't hard-code magic numbers; users on tight memory budgets need to reach in and adjust them.
  • Don't grow the public surface lightly. Every new public symbol or lwow_ll_drv_t member is something every user has to either implement or compile out. New API needs a justification beyond "it would be convenient".

Repository layout

lwow/                              Library subtree (this is what consumers vendor)
├── CMakeLists.txt                 Wrapper that includes library.cmake
├── library.cmake                  Source/include/define lists for CMake users
└── src/
    ├── include/
    │   ├── lwow/
    │   │   ├── lwow.h             Public API: \defgroup LWOW + LWOW_LL
    │   │   ├── lwow_opt.h         Default config + \defgroup LWOW_OPT
    │   │   ├── lwow_opts_template.h   Template the user copies to lwow_opts.h
    │   │   └── devices/
    │   │       └── lwow_device_ds18x20.h   \defgroup LWOW_DEVICE_DS18x20
    │   └── system/
    │       └── lwow_sys.h         OS abstraction \defgroup LWOW_SYS
    ├── lwow/lwow.c                Core implementation
    ├── devices/lwow_device_ds18x20.c
    └── system/                    Reference low-level drivers (one per platform)
        ├── lwow_ll_win32.c        WIN32 COM-port reference
        ├── lwow_ll_stm32.c        STM32 LL reference
        ├── lwow_ll_stm32_hal.c    STM32 HAL reference
        ├── lwow_ll_stm32_single_gpio_driver.c   GPIO bit-bang fallback
        ├── lwow_ll_stm32{l496g_discovery,f429zi_nucleo,f401re_nucleo,l0xx}.c
        ├── lwow_sys_win32.c
        ├── lwow_sys_cmsis_os.c    CMSIS-OS v2 mutex impl
        └── lwow_sys_threadx.c     Azure RTOS ThreadX mutex impl

dev/                               Top-level dev/test harness used when this repo is built standalone
examples/                          Standalone examples (WIN32, STM32 CubeIDE)
snippets/                          Reusable code snippets linked from examples
docs/                              Documentation sources
cmake/                             CMake helper modules

When this repository is opened standalone, CMakeLists.txt builds the WIN32 dev harness in dev/main.c. When consumed via add_subdirectory(lwow), only the lwow and lwow_devices (and optionally lwow_snippets) targets are produced.

Architecture invariants

  • lwow_ll_drv_t is exactly four functions: init, deinit, set_baudrate, tx_rx. Every port has to implement all of them — adding a fifth breaks every existing port.
  • Thread safety is opt-in via LWOW_CFG_OS. When set, the user must implement four lwow_sys_mutex_* functions and define LWOW_CFG_OS_MUTEX_HANDLE. When unset, none of the lwow_sys_* symbols are referenced — bare-metal builds pay zero cost.
  • Configuration override pattern. Users supply lwow_opts.h (copied from lwow_opts_template.h) on the include path; LWOW_OPTS_FILE CMake variable points at it. To skip the user file entirely, define LWOW_IGNORE_USER_OPTS.

Coding rules

This project follows the Lw* family C style: https://github.com/MaJerle/c-code-style. Read it before submitting non-trivial changes.

clang-format is configured at .clang-format and clang-tidy at .clang-tidy. Format every change before committing. The format file targets clang-format >= 20.1. Concrete rules from the config (don't fight them — they're enforced):

  • Indent: 4 spaces, no tabs (IndentWidth: 4, UseTab: Never).
  • Line length: 120 columns (ColumnLimit: 120).
  • Braces: attached / 1TBS (BreakBeforeBraces: Attach); braces are required on every control statement, even one-liners (InsertBraces: true).
  • Pointer alignment: left (PointerAlignment: Left) — write lwow_t* obj, not lwow_t *obj.
  • Return type on its own line for definitions (AlwaysBreakAfterReturnType: AllDefinitions).
  • Includes are sorted (SortIncludes: true).

Naming and identifier conventions (observed throughout the codebase):

  • snake_case for functions, variables, struct members.
  • Type names get a _t suffix (lwow_t, lwow_rom_t, lwow_ll_drv_t).
  • Function type names get a _fn suffix (lwow_search_cb_fn).
  • Public symbols are prefixed lwow_; macros and config defines use LWOW_.
  • const-qualify pointer parameters where applicable (lwow_t* const owobj).

Doxygen documentation rules (strict)

The C API reference is generated from Doxygen comments. Documentation parameter names must exactly match the actual parameter names in the signature — both spelling and order. The recently-fixed ow vs owobj mismatch in lwow_search_cb_fn is the kind of thing that gets flagged.

  • Use JavaDoc-style comments with \brief, \param[in/out] name: description, \return, \note, \sa. The codebase uses backslash form (\brief), not at-sign (@brief); stay consistent.
  • Public API entities belong to a \defgroup so they appear under the right module in the documentation tree. Existing groups: LWOW, LWOW_OPT, LWOW_LL, LWOW_SYS, LWOW_DEVICE_DS18x20. New code joins one via \ingroup or defines a new group with a clear parent.
  • Conditional API surface uses #ifdef DOXYGEN_SHOULD_SKIP_THIS (already in the Doxyfile PREDEFINED).

Quick local check that documented \param names line up with signatures:

grep -nE '\\param(\[[^]]+\])?\s+\w+:' lwow/src/include/**/*.h

For each function/typedef, every documented \param NAME must appear in the actual parameter list, in the same order.

Build & integration

# Build the WIN32 dev harness from a clean checkout
cmake --preset default
cmake --build --preset default

Presets are defined in CMakePresets.json. The dev harness lives in dev/main.c and links against the WIN32 reference port (lwow_ll_win32.c + lwow_sys_win32.c).

When integrating LwOW into another (embedded) project:

set(LWOW_OPTS_FILE ${CMAKE_CURRENT_LIST_DIR}/path/to/lwow_opts.h)
add_subdirectory(third_party/lwow/lwow)
target_link_libraries(your_target PRIVATE lwow lwow_devices)

For non-CMake toolchains (typical of vendor IDEs like STM32CubeIDE, Keil, IAR): add lwow/src/include to the compiler's include path, compile the sources under lwow/src/lwow/ and lwow/src/devices/, and supply lwow_opts.h somewhere on the include path.

Documentation

Documentation sources live under docs/. The site is published on Read the Docs at https://docs.majerle.eu/projects/lwow/. Build configuration lives in .readthedocs.yaml. Don't edit generated artifacts; edit the sources in docs/ and let the build pipeline regenerate.

When you change public API:

  1. Update the Doxygen comment on the declaration (parameter names included — see above).
  2. Re-check the relevant \defgroup so new symbols appear in the right module page.
  3. If user-visible behaviour changed, add a CHANGELOG.md entry.

Branching & contributions

  • Default working branch: develop. PRs target develop, not main.
  • main/master is the stable branch — only release merges land there.
  • Keep commits focused; one logical change per PR.

What NOT to do

  • Don't add platform #includes to core or device files. Platform contact belongs only in lwow/src/system/ ports.
  • Don't add malloc/free or any heap allocation. Storage is supplied by the caller.
  • Don't pull in <stdio.h>, <stdlib.h>, <math.h>, or other hosted-environment headers in core code.
  • Don't introduce floating point outside the documented DS18x20 read API.
  • Don't grow lwow_ll_drv_t without an architectural discussion — every existing port has to be updated.
  • Don't bypass lwow_opt.h for new tunables; embedded users expect every knob to be overridable from lwow_opts.h.
  • Don't reformat unrelated code; keep diffs minimal and let clang-format handle whitespace on the lines you actually changed.
  • Don't ship undocumented public API — the generated reference would have a hole.
  • Don't claim work is done without running clang-format over your edits.