Skip to content

Commit 26d121b

Browse files
Lukasz-JuranekLJ
authored andcommitted
Initial import of open-sent-c
SAE J2716 SENT protocol implementation in C, extracted from the SENTToUSB STM32F042 firmware. Layout: public headers at root, sources and platform ports under implementation/.
0 parents  commit 26d121b

36 files changed

Lines changed: 42414 additions & 0 deletions

.github/workflows/test.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: tests
2+
3+
on:
4+
push:
5+
branches: [master, main]
6+
pull_request:
7+
branches: [master, main]
8+
9+
jobs:
10+
unit-tests:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Install dependencies
16+
run: |
17+
sudo apt-get update
18+
sudo apt-get install -y gcc gcovr
19+
20+
- name: Build tests
21+
working-directory: implementation/Tests
22+
run: make all
23+
24+
- name: Run tests
25+
working-directory: implementation/Tests
26+
run: make run
27+
28+
- name: Coverage summary (gcov)
29+
working-directory: implementation/Tests
30+
run: make coverage
31+
32+
- name: Coverage report (gcovr)
33+
working-directory: implementation/Tests
34+
run: |
35+
gcovr build/lib --root ../.. \
36+
--exclude '.*hal_host\.c' \
37+
--exclude '.*hal_stm32f042\.c' \
38+
--exclude-lines-by-pattern '.*GCOV_EXCL_LINE.*' \
39+
--exclude-branches-by-pattern '.*GCOV_EXCL_BR_LINE.*' \
40+
--exclude-branches-by-pattern '.*SENT_ASSERT.*' \
41+
--exclude-branches-by-pattern '.*manager != NULL.*' \
42+
--gcov-ignore-errors=no_working_dir_found \
43+
--print-summary \
44+
--txt build/coverage.txt \
45+
--xml build/coverage.xml
46+
cat build/coverage.txt
47+
48+
- name: Upload coverage artifact
49+
if: always()
50+
uses: actions/upload-artifact@v4
51+
with:
52+
name: coverage
53+
path: implementation/Tests/build/coverage.*
54+
retention-days: 90

.gitignore

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Build artefacts
2+
*.o
3+
*.obj
4+
*.a
5+
*.lib
6+
*.so
7+
*.so.*
8+
*.dylib
9+
*.dll
10+
*.exe
11+
*.elf
12+
*.bin
13+
*.hex
14+
*.map
15+
*.lst
16+
*.d
17+
18+
# Build directories
19+
build/
20+
out/
21+
dist/
22+
cmake-build-*/
23+
24+
# Editor / IDE
25+
.vscode/
26+
.idea/
27+
*.swp
28+
*~
29+
.cache/
30+
compile_commands.json
31+
32+
# OS
33+
.DS_Store
34+
Thumbs.db
35+
36+
# Coverage / debug
37+
*.gcno
38+
*.gcda
39+
*.gcov
40+
coverage/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 ucandevices
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# open-sent-c
2+
3+
[![tests](https://github.com/ucandevices/open-sent-c/actions/workflows/test.yml/badge.svg)](https://github.com/ucandevices/open-sent-c/actions/workflows/test.yml)
4+
5+
A portable C implementation of the SAE J2716 SENT (Single Edge Nibble Transmission)
6+
protocol for embedded systems and host-side tooling.
7+
8+
## Layout
9+
10+
```
11+
.
12+
├── *.h public API headers
13+
└── implementation/ library sources (.c) and platform ports
14+
```
15+
16+
The protocol layer (`sent_protocol`, `sent_encoder`, `sent_decoder`, `sent_crc`)
17+
is MCU-agnostic and uses the `hal.h` function-pointer interface to talk to
18+
hardware. Platform-specific ports live alongside the protocol code:
19+
20+
- `hal_stm32f042.{h,c}` — STM32F042 (TIM input-capture RX, software TX) port
21+
- `hal_host.{h,c}` — host-side stub for unit tests
22+
23+
## Public API
24+
25+
- `sent_protocol.h` — frame/config types, nibble packing
26+
- `sent_encoder.h` — frame → tick intervals → microsecond timestamps
27+
- `sent_decoder.h` — microsecond timestamps → decoded frame
28+
- `sent_crc.h` — SAE J2716 4-bit CRC
29+
- `mode_manager.h` — RX/TX/STOPPED state machine + statistics
30+
- `hal.h` / `hal_config.h` — RX/TX HAL interfaces a port must implement
31+
32+
## Usage
33+
34+
The library has no build system of its own — drop the headers on your
35+
include path and compile the relevant `.c` files in `implementation/`
36+
into your project. Pick the HAL port that matches your target (or write
37+
your own against `hal.h`).
38+
39+
## Testing
40+
41+
Unit tests live in `implementation/Tests/` and are built with plain GCC
42+
(`-std=c99`). They use a single-header framework (`test.h`) with no external
43+
dependencies.
44+
45+
```sh
46+
cd implementation/Tests
47+
make run # build and run all tests
48+
make coverage # run tests + enforce 100% line and branch coverage
49+
```
50+
51+
Coverage is measured with **gcov/gcovr**. The `make coverage` target requires
52+
both tools to be on `PATH` (`apt-get install gcc gcovr` on Debian/Ubuntu) and
53+
fails if either metric drops below 100%.
54+
55+
### CI
56+
57+
A GitHub Actions workflow (`.github/workflows/test.yml`) runs on every push
58+
and pull request to `master`/`main`:
59+
60+
1. Builds the test binary with `-O0 --coverage`.
61+
2. Runs all 126 tests — the job fails on any failing test.
62+
3. Generates a gcovr XML + text report and uploads it as a build artifact.
63+
64+
Genuinely unreachable branches (dead code protected by configuration
65+
invariants) are annotated with `/* GCOV_EXCL_BR_LINE */` or suppressed via
66+
gcovr pattern filters so they do not inflate the denominator.
67+
68+
### Coverage artifacts
69+
70+
After each CI run the coverage report is uploaded as a GitHub Actions artifact
71+
named **`coverage`** and retained for 90 days. To download it:
72+
73+
1. Open the [Actions tab](https://github.com/ucandevices/open-sent-c/actions).
74+
2. Click any completed workflow run.
75+
3. Scroll to the **Artifacts** section at the bottom of the page and download `coverage.zip`.
76+
77+
The zip contains:
78+
- `coverage.txt` — human-readable summary table
79+
- `coverage.xml` — Cobertura XML (compatible with SonarQube, Codecov, VS Code extensions)
80+
81+
## Reference integration
82+
83+
[**SENTToUSB**](https://github.com/ucandevices/SENTToUSB) is a reference
84+
firmware project that integrates this library on an STM32F042 to expose a
85+
SENT sensor as a USB CDC / CAN device. It demonstrates the full stack:
86+
HAL port (`hal_stm32f042`) wired to real hardware timers and CAN-frame
87+
transport over USB.
88+
89+
## License
90+
91+
MIT — see [LICENSE](LICENSE).

hal.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#ifndef SENT_HAL_H
2+
#define SENT_HAL_H
3+
4+
#include <stdbool.h>
5+
#include <stddef.h>
6+
#include <stdint.h>
7+
8+
#include "sent/hal_config.h"
9+
#include "sent/sent_protocol.h"
10+
11+
#ifdef __cplusplus
12+
extern "C" {
13+
#endif
14+
15+
typedef bool (*sent_rx_start_fn)(void* context);
16+
typedef void (*sent_rx_stop_fn)(void* context);
17+
typedef bool (*sent_rx_poll_timestamps_fn)(void* context,
18+
uint32_t* out_timestamps_us,
19+
size_t* inout_timestamp_count);
20+
/* Optional: update capture_batch_size when data_nibbles changes.
21+
* Called by bridge config handler and bridge_start_rx. May be NULL. */
22+
typedef void (*sent_rx_set_data_nibbles_fn)(void* context, uint8_t data_nibbles);
23+
24+
/* Optional: update sync-detection threshold [µs] to match the configured tick
25+
* range. Called by bridge when min/max_tick_x10_us changes. May be NULL. */
26+
typedef void (*sent_rx_set_sync_min_us_fn)(void* context, uint32_t sync_min_us);
27+
28+
typedef bool (*sent_tx_start_fn)(void* context);
29+
typedef void (*sent_tx_stop_fn)(void* context);
30+
typedef bool (*sent_tx_submit_frame_fn)(void* context,
31+
const sent_frame_t* frame,
32+
const sent_config_t* config,
33+
uint16_t pause_ticks);
34+
/* Optional: set the TX tick period (HAL reprograms its timebase).
35+
* tick_x10_us is in 0.1-us units (e.g. 30 = 3.0 us). May be NULL. */
36+
typedef bool (*sent_tx_set_tick_fn)(void* context, uint16_t tick_x10_us);
37+
38+
typedef struct {
39+
void* context;
40+
sent_rx_start_fn start_rx;
41+
sent_rx_stop_fn stop_rx;
42+
sent_rx_poll_timestamps_fn poll_timestamps_us;
43+
sent_rx_set_data_nibbles_fn set_data_nibbles; /* optional, NULL if not supported */
44+
sent_rx_set_sync_min_us_fn set_sync_min_us; /* optional, NULL if not supported */
45+
} sent_rx_hal_t;
46+
47+
typedef struct {
48+
void* context;
49+
sent_tx_start_fn start_tx;
50+
sent_tx_stop_fn stop_tx;
51+
sent_tx_submit_frame_fn submit_frame;
52+
sent_tx_set_tick_fn set_tick_x10_us; /* optional, NULL if not supported */
53+
} sent_tx_hal_t;
54+
55+
#ifdef __cplusplus
56+
}
57+
#endif
58+
59+
#endif /* SENT_HAL_H */

hal_config.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#ifndef SENT_HAL_CONFIG_H
2+
#define SENT_HAL_CONFIG_H
3+
4+
#if defined(SENT_HAL_HOST) && defined(SENT_HAL_STM32F042)
5+
#error "Only one HAL backend can be enabled at a time."
6+
#endif
7+
8+
#if !defined(SENT_HAL_HOST) && !defined(SENT_HAL_STM32F042)
9+
#if defined(STM32F042x6)
10+
#define SENT_HAL_STM32F042 1
11+
#else
12+
#define SENT_HAL_HOST 1
13+
#endif
14+
#endif
15+
16+
#endif /* SENT_HAL_CONFIG_H */

hal_host.h

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#ifndef SENT_HAL_HOST_H
2+
#define SENT_HAL_HOST_H
3+
4+
#include <stdbool.h>
5+
#include <stddef.h>
6+
#include <stdint.h>
7+
8+
#include "sent/hal.h"
9+
10+
#if defined(SENT_HAL_HOST)
11+
#include <pthread.h>
12+
#endif
13+
14+
#ifdef __cplusplus
15+
extern "C" {
16+
#endif
17+
18+
#define SENT_HOST_RX_MAX_BATCHES 64U
19+
#define SENT_HOST_RX_MAX_TIMESTAMPS 32U
20+
21+
typedef struct {
22+
uint32_t timestamps_us[SENT_HOST_RX_MAX_TIMESTAMPS];
23+
size_t count;
24+
} sent_host_rx_batch_t;
25+
26+
typedef struct {
27+
#if defined(SENT_HAL_HOST)
28+
pthread_mutex_t lock;
29+
#endif
30+
bool running;
31+
sent_host_rx_batch_t queue[SENT_HOST_RX_MAX_BATCHES];
32+
size_t head;
33+
size_t tail;
34+
} sent_host_rx_hal_t;
35+
36+
typedef struct {
37+
#if defined(SENT_HAL_HOST)
38+
pthread_mutex_t lock;
39+
#endif
40+
bool running;
41+
bool has_last_frame;
42+
sent_frame_t last_frame;
43+
uint16_t last_intervals_ticks[SENT_MAX_INTERVALS];
44+
size_t last_intervals_count;
45+
} sent_host_tx_hal_t;
46+
47+
void sent_host_rx_hal_init(sent_host_rx_hal_t* hal);
48+
void sent_host_rx_hal_deinit(sent_host_rx_hal_t* hal);
49+
bool sent_host_rx_hal_inject(sent_host_rx_hal_t* hal,
50+
const uint32_t* timestamps_us,
51+
size_t timestamp_count);
52+
bool sent_host_rx_hal_running(const sent_host_rx_hal_t* hal);
53+
size_t sent_host_rx_hal_pending_batches(const sent_host_rx_hal_t* hal);
54+
void sent_host_make_rx_hal(sent_host_rx_hal_t* impl, sent_rx_hal_t* out_hal);
55+
56+
void sent_host_tx_hal_init(sent_host_tx_hal_t* hal);
57+
void sent_host_tx_hal_deinit(sent_host_tx_hal_t* hal);
58+
bool sent_host_tx_hal_running(const sent_host_tx_hal_t* hal);
59+
bool sent_host_tx_hal_last_frame(const sent_host_tx_hal_t* hal, sent_frame_t* out_frame);
60+
size_t sent_host_tx_hal_last_intervals(const sent_host_tx_hal_t* hal,
61+
uint16_t* out_intervals,
62+
size_t max_count);
63+
void sent_host_make_tx_hal(sent_host_tx_hal_t* impl, sent_tx_hal_t* out_hal);
64+
65+
#ifdef __cplusplus
66+
}
67+
#endif
68+
69+
#endif /* SENT_HAL_HOST_H */

0 commit comments

Comments
 (0)