Guidance for AI coding agents (Claude Code, Cursor, Copilot, etc.) working in this repository.
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 —
9600baud for the reset pulse,115200baud 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.
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>formemcpy/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 acrosslwow/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 behindlwow_ll_drv_t(UART) and thelwow_sys_*API (OS mutex). Reference port files underlwow/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_readandlwow_ds18x20_read_rawexposefloat*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 defaultso the user can override it fromlwow_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_tmember is something every user has to either implement or compile out. New API needs a justification beyond "it would be convenient".
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.
lwow_ll_drv_tis 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 fourlwow_sys_mutex_*functions and defineLWOW_CFG_OS_MUTEX_HANDLE. When unset, none of thelwow_sys_*symbols are referenced — bare-metal builds pay zero cost. - Configuration override pattern. Users supply
lwow_opts.h(copied fromlwow_opts_template.h) on the include path;LWOW_OPTS_FILECMake variable points at it. To skip the user file entirely, defineLWOW_IGNORE_USER_OPTS.
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) — writelwow_t* obj, notlwow_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_casefor functions, variables, struct members.- Type names get a
_tsuffix (lwow_t,lwow_rom_t,lwow_ll_drv_t). - Function type names get a
_fnsuffix (lwow_search_cb_fn). - Public symbols are prefixed
lwow_; macros and config defines useLWOW_. const-qualify pointer parameters where applicable (lwow_t* const owobj).
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
\defgroupso 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\ingroupor defines a new group with a clear parent. - Conditional API surface uses
#ifdef DOXYGEN_SHOULD_SKIP_THIS(already in the DoxyfilePREDEFINED).
Quick local check that documented \param names line up with signatures:
grep -nE '\\param(\[[^]]+\])?\s+\w+:' lwow/src/include/**/*.hFor each function/typedef, every documented \param NAME must appear in the actual parameter list, in the same order.
# Build the WIN32 dev harness from a clean checkout
cmake --preset default
cmake --build --preset defaultPresets 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 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:
- Update the Doxygen comment on the declaration (parameter names included — see above).
- Re-check the relevant
\defgroupso new symbols appear in the right module page. - If user-visible behaviour changed, add a CHANGELOG.md entry.
- Default working branch:
develop. PRs targetdevelop, notmain. main/masteris the stable branch — only release merges land there.- Keep commits focused; one logical change per PR.
- Don't add platform
#includes to core or device files. Platform contact belongs only inlwow/src/system/ports. - Don't add
malloc/freeor 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_twithout an architectural discussion — every existing port has to be updated. - Don't bypass
lwow_opt.hfor new tunables; embedded users expect every knob to be overridable fromlwow_opts.h. - Don't reformat unrelated code; keep diffs minimal and let
clang-formathandle 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-formatover your edits.