Getting Started¶
Adding SyntropicOS to Your Project¶
As a Git Submodule¶
cd your_project
git submodule add https://github.com/outlookhazy/SyntropicOS lib/SyntropicOS
git submodule update --init
Build System Integration¶
In your project's CMakeLists.txt:
add_subdirectory(lib/SyntropicOS)
target_link_libraries(your_target PRIVATE syntropic)
# Optional: include weak port stubs for development
target_link_libraries(your_target PRIVATE syn_stubs)
The syntropic target is an INTERFACE library — it adds the include path and source files to your target, compiled with your project's own flags and toolchain. No separate compilation step, no flag mismatches.
SYN_DIR := lib/SyntropicOS
include $(SYN_DIR)/sources.mk
CFLAGS += -I$(SYN_INC)
SRCS += $(SYN_SRCS)
# Optional: weak stubs
SRCS += $(SYN_STUB_SRCS)
sources.mk exports three variables:
SYN_SRCS— all SyntropicOS.csource filesSYN_STUB_SRCS— weak port stubsSYN_INC— include path (src/subdirectory)
- Add
lib/SyntropicOS/to your include paths. - Add the
.cfiles from thesrc/syntropic/subdirectories you need to your build. - Optionally add
src/syntropic/port_stubs/syn_port_stubs.c.
Configuration¶
SyntropicOS is configured through a single header file, syn_config.h, placed on your include path.
Creating Your Config¶
How It Works¶
The umbrella header (syntropic/syntropic.h) checks for each module using this pattern:
This means:
- If
SYN_USE_GPIOis not defined → module is enabled (included by default) - If
SYN_USE_GPIOis defined as1→ module is enabled - If
SYN_USE_GPIOis defined as0→ module is disabled
No config file? No problem.
If no syn_config.h is found on the include path, all modules default to enabled. This is useful for quick prototyping, but you'll want a config file in production to minimize code size.
Module Switches¶
Edit syn_config.h to enable or disable modules:
/* Drivers */
#define SYN_USE_GPIO 1
#define SYN_USE_UART 1
#define SYN_USE_ADC 1
#define SYN_USE_EXTI 1
/* Multitasking */
#define SYN_USE_PT 1
#define SYN_USE_SCHED 1
#define SYN_USE_TIMER 1
#define SYN_USE_EVENT 1
#define SYN_USE_WORKQUEUE 1
/* Services */
#define SYN_USE_LOG 1
#define SYN_USE_CLI 1
/* I/O */
#define SYN_USE_BUTTON 1
#define SYN_USE_LED 1
#define SYN_USE_ENCODER 1
/* Control & Motor */
#define SYN_USE_PID 1
#define SYN_USE_STEPPER 1
#define SYN_USE_SERVO 1
#define SYN_USE_DC_MOTOR 1
#define SYN_USE_MOTOR_CTRL 1
/* DSP */
#define SYN_USE_FILTER 1
#define SYN_USE_SIGNAL 1
#define SYN_USE_FSM 1
/* Communication */
#define SYN_USE_COBS 1
#define SYN_USE_MODBUS 1
Tuning Parameters¶
Beyond module switches, several modules expose tuning knobs:
| Parameter | Default | Description |
|---|---|---|
SYN_LOG_LEVEL |
1 (DEBUG) |
Compile-time minimum log level (0=TRACE .. 5=FATAL, 6=NONE) |
SYN_LOG_BUF_SIZE |
192 |
Log output buffer size in bytes |
SYN_LOG_TIMESTAMP |
1 |
Include [tick] timestamp prefix in log output |
SYN_LOG_COLOR |
0 |
Enable ANSI color codes in log output |
SYN_CLI_LINE_BUF_SIZE |
128 |
Maximum command line length |
SYN_CLI_MAX_ARGS |
16 |
Maximum argc (including command name) |
SYN_CLI_HISTORY_DEPTH |
0 |
Command history depth (0 = disabled) |
SYN_UART_TX_BUF_SIZE |
128 |
UART TX ring buffer size in bytes |
SYN_UART_RX_BUF_SIZE |
128 |
UART RX ring buffer size in bytes |
SYN_UART_MAX_INSTANCES |
2 |
Maximum simultaneous UART handles |
SYN_FILTER_MAX_WINDOW |
32 |
Maximum filter window size |
SYN_CRC_USE_TABLE |
1 |
1 = fast lookup table, 0 = small bitwise computation |
SYN_GFX_BACKEND |
CANVAS |
Graphics backend: SYN_GFX_BACKEND_CANVAS (framebuffer) or SYN_GFX_BACKEND_DIRECT (no framebuffer) |
Assert Configuration¶
SyntropicOS uses SYN_ASSERT() throughout for runtime safety checks. To strip all asserts in release builds:
Quick Example¶
A minimal blink task using the cooperative scheduler:
#include "syntropic/syntropic.h"
#define TAG "main"
static SYN_PT_Status blink_task(SYN_PT *pt, SYN_Task *task)
{
PT_BEGIN(pt);
for (;;) {
syn_gpio_toggle(LED_PIN);
SYN_LOG_D(TAG, "blink");
PT_TASK_DELAY_MS(pt, task, 500);
}
PT_END(pt);
}
int main(void)
{
syn_gpio_init(LED_PIN, SYN_GPIO_OUTPUT);
syn_log_init(my_uart_output, SYN_LOG_DEBUG);
static SYN_Task tasks[1];
static SYN_Sched sched;
syn_task_create(&tasks[0], "blink", blink_task, 0, NULL);
syn_sched_init(&sched, tasks, 1);
syn_sched_run_forever(&sched);
}
What's happening here:
PT_BEGIN/PT_ENDwrap the protothread body. TheSYN_PTstruct costs 2 bytes of RAM — it stores auint16_tline continuation using Duff's device (switch/__LINE__).PT_TASK_DELAY_MSsaves a deadline tick intask->delay_untiland yields. On each subsequent scheduler tick, the protothread resumes at this line and checks ifsyn_port_get_tick_ms() >= deadline. No blocking, no busy-wait.syn_task_createsets priority0(highest). Each call tosyn_sched_run()selects and runs the single highest-priority ready task, with round-robin among equal priorities.syn_sched_run_foreverloops forever callingsyn_sched_run(). The scheduler does not own the task array — you allocate it, keeping everything on the stack or in static memory.
Local variables in protothreads
Local variables are not preserved across yield/wait points (because there's no stack save). Store persistent state in static variables or in a struct passed via task->user_data.
Next Steps¶
- Implement the port layer for your MCU
- Browse the module reference to see what's available
- Run the test suite to verify your setup