Coding Style¶
This page explains the coding style PEROVSAT uses for its C and C++ code: what that style is, how to confirm it for yourself, and what you need to do to follow it. If you have ever wondered why the tabs are so wide or whether a brace goes on the same line or the next one, this is the page to read.
Summary¶
PEROVSAT C/C++ code follows the Zephyr coding style, which is the Linux kernel coding style with a few small adjustments. If you already know how Linux kernel code looks, you already know how our code should look.
We did not define a style of our own. We adopted Zephyr's, because all of our flight code is built on top of Zephyr, and keeping one consistent style across the whole tree is far easier to read than switching conventions between the RTOS and our own files.
Confirming the style¶
You do not have to take this on trust. The evidence sits in the config files at
the root of every C/C++ repo (amu-driver, perovsat-app, mpu6050-driver).
The key settings in .clang-format are:
| Setting | Value | What it indicates |
|---|---|---|
IndentWidth |
8 |
Eight columns per indent level, the classic kernel width |
UseTab |
ForContinuationAndIndentation |
Real tab characters, not spaces |
BreakBeforeBraces |
Linux |
The named "Linux" brace preset in clang-format |
ColumnLimit |
100 |
The modern kernel and Zephyr line limit |
IndentCaseLabels |
false |
case labels line up under switch, kernel style |
BreakBeforeBraces: Linux is the clearest signal. It is a named preset in
clang-format that exists specifically to reproduce Linux kernel brace placement.
Beyond that, the comment header in our .clang-format reads "PerovSat
clang-format style (Zephyr-aligned)" and points at zephyr/.clang-format, and
the .editorconfig header notes that it is "Aligned with Zephyr conventions".
Zephyr's own contributor documentation states that Zephyr follows the Linux
kernel coding style. The chain is therefore straightforward: PEROVSAT follows
Zephyr, and Zephyr follows Linux.
To verify this directly, compare our .clang-format against zephyr/.clang-format
in the west workspace. They are intentionally almost identical; Zephyr only lists
a few extra ForEachMacros for subsystems we do not use.
The rules in practice¶
You rarely have to apply these by hand, since clang-format does it for you (see Applying the style below). Still, it helps to know what the formatter is aiming for so its output does not catch you off guard.
Indentation¶
- Indent with hard tabs, and treat a tab as 8 columns.
- Continuation lines (a statement wrapped onto the next line) are indented an extra 8 columns.
- If code drifts so far to the right that 8-wide tabs become painful, take that as a signal that the function is doing too much. Extract logic into a helper rather than fighting the indentation.
Braces¶
This is the rule most often gotten wrong, so it is worth stating plainly:
- Functions get their opening brace on its own line.
- Everything else (
if,for,while,switch,struct, and so on) gets the opening brace on the same line, after a single space.
/* function: brace drops to the next line */
static int sample_fetch(const struct device *dev, enum sensor_channel channel)
{
struct mpu6050_data *data = dev->data;
/* control statement: brace stays on the same line */
if (channel != SENSOR_CHAN_ALL) {
return -ENOTSUP;
}
return 0;
}
Braces are also mandatory. InsertBraces: true means clang-format adds braces to
a one-line if even if you leave them off, so there is no point writing
brace-less bodies.
Line length¶
Lines stop at 100 columns. Both .clang-format and .editorconfig enforce
this. Commit message lines are kept shorter, at 75 columns, which matches normal
Git convention.
switch statements¶
case labels are not indented past the switch. They sit at the same level,
and the body of each case is indented one tab in:
switch (device_type) {
case DEVICE_TYPE_MPU6500:
tmp_val = (tmp_val * 1000 / 333870) + 21000000;
break;
case DEVICE_TYPE_MPU6050:
default:
tmp_val = (tmp_val / 340) + 36000000;
break;
}
Includes¶
We do not sort includes alphabetically (SortIncludes: Never), but clang-format
does group them by category, and the order is:
- Local headers in quotes, like
"mpu6050.h" - Standard C library headers, like
<errno.h> - Zephyr headers, like
<zephyr/device.h> - Everything else
Put your own header first, and let the formatter handle the spacing between groups.
File hygiene¶
- Files end with a single newline (
InsertNewlineAtEOF: true). - No trailing whitespace on any line.
- Encoding is UTF-8 and line endings are LF (Unix), never CRLF.
Indentation per file type¶
The C rules above are for actual C/C++ source. Other file types in the repo use
their own conventions, all set in .editorconfig:
| File type | Indent | Width |
|---|---|---|
*.c, *.h, *.cpp, *.hpp, *.S, *.ld |
tab | 8 |
Devicetree (*.dts, *.dtsi, *.overlay) |
tab | 8 |
Kconfig* |
tab | 8 |
Python (*.py) |
space | 4 |
Shell (*.sh) |
space | 4 |
YAML (*.yml, *.yaml) |
space | 2 |
CMake (CMakeLists.txt, *.cmake) |
space | 2 |
In short, C-family and hardware description files use tabs, while scripting and build-config files use spaces. Do not reformat a Python helper with tabs just because the C files use them.
The files that enforce the style¶
Three files do the work. They live at the root of each C/C++ repo and are copied
from the driver-template, so every repo gets the same setup.
.clang-formatis the formatter config. It is the source of truth for spacing, braces, and wrapping..editorconfigtells your editor about tabs, line endings, charset, and per-filetype indent so files look right while you type, before you ever run clang-format..pre-commit-config.yamlwires up the automatic checks (see below).
Applying the style¶
You should almost never format code by hand. Let the tooling do it.
One-time setup¶
Pre-commit hooks are installed for you when you run perovsat-app/setup.sh. If
you cloned a repo on its own, install the hooks once from the repo root:
pip install pre-commit
pre-commit install
Most editors pick up .editorconfig automatically. For clang-format, install
the matching version (we pin v22 through the pre-commit mirror) and turn on
"format on save" if your editor supports it.
Formatting your changes¶
To format everything before a commit:
pre-commit run --all-files
To format a single file directly with clang-format:
clang-format -i path/to/file.c
The -i flag edits the file in place.
What the hooks check¶
The pre-commit config runs three hooks on every commit:
| Hook | What it does |
|---|---|
trailing-whitespace |
strips trailing spaces from all text files |
end-of-file-fixer |
makes sure files end with exactly one newline |
clang-format |
reformats C and C++ to match .clang-format |
If a hook changes a file, the commit is stopped so you can review the change and stage it. Run the commit again once you have staged it.
Writing readable code¶
clang-format handles layout, but it cannot make code clear on its own. The kernel style assumes a few habits that the formatter cannot enforce:
- Keep functions short enough to read on a single screen.
- Treat the 8-wide indentation as a warning sign. Deep nesting usually means a function should be split up.
- Write comments that explain why, not what. The example files in
mpu6050-driverare a good reference for tone.
Scope and open questions¶
This page covers C and C++, where almost all of our embedded code lives. A few points are still being settled:
- Naming conventions (functions, structs, macros) beyond what clang-format touches are not formally documented yet. For now, match the surrounding Zephyr-style code: lower_snake_case for functions and variables, UPPER_CASE for macros and enum values.
- The exact CI pipeline that runs these checks on pull requests is not yet finalized. Locally, the pre-commit hooks are what to rely on.
When you add a new repo, copy .clang-format, .editorconfig, and
.pre-commit-config.yaml from an existing one so it inherits the same style.