Skip to content

Commit 4e384a3

Browse files
committed
Add AI helper skills
1 parent c59b06e commit 4e384a3

24 files changed

Lines changed: 7148 additions & 0 deletions

File tree

.agents/skills/c-style/SKILL.md

Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
---
2+
name: c-style
3+
description: Enforce and review this project's C/C++ coding style for RP2040/RP2350 Pico SDK firmware. Use this skill whenever writing, editing, or reviewing .c, .h, .cpp, .hpp, CMakeLists, Pico SDK module code, FreeRTOS task/queue/semaphore code, u8g2/MUI display code, lwIP/CYW43 wireless or REST endpoint code, EEPROM configuration code, PIO/UART/I2C/SPI/PWM driver code, or embedded target configuration in this repository. Apply it proactively for C/C++ refactors, bug fixes, feature work, and code-review requests; do not wait for the user to ask for style explicitly.
4+
---
5+
6+
# C/C++ Style Guide
7+
8+
This skill captures the C/C++ style used in this repository. Apply it proactively when writing or reviewing firmware code.
9+
10+
This project is Pico SDK firmware for RP2040/RP2350-class boards, with FreeRTOS, u8g2/MUI, lwIP/CYW43 networking, EEPROM-backed configuration, PIO stepper control, and small C++ bridge islands. The strongest style signal is tracked project code under `src/` and target hardware configuration under `targets/`.
11+
12+
Treat `library/` code as third-party unless the user explicitly asks to modify it. Generated files under `src/generated/` should normally be regenerated rather than hand edited.
13+
14+
The style is direct embedded C with pragmatic C++ where it helps integrate C++ helpers or device libraries: explicit initialization, visible hardware control, clear task/queue flow, and small abstractions only where the surrounding module already supports them.
15+
16+
---
17+
18+
## Rule 1 - Match the local module shape
19+
20+
Keep code organized as small module pairs in `src/`: `feature.c` / `feature.h`, or `feature.cpp` / `feature.h` when C++ is required. Public APIs are declared in the header and implemented in the source file. File-local helpers, callbacks, buffers, task handles, and state should be `static` unless existing code deliberately shares them.
21+
22+
Prefer existing module vocabulary and prefixes:
23+
24+
- `*_init`, `*_config_init`, `*_config_save`
25+
- `*_task`, `*_render_task`, `*_wait_for_*`
26+
- `http_rest_*`, `http_get_*`, `populate_rest_*`, `apply_rest_*`
27+
- `*_set_*`, `get_*`, `*_enable`
28+
- hardware names such as `motor_*`, `scale_*`, `servo_gate_*`, `wireless_*`, `neopixel_led_*`
29+
30+
**Wrong:**
31+
```c
32+
void update(void) {
33+
...
34+
}
35+
```
36+
37+
**Right:**
38+
```c
39+
static void wireless_update_state(void) {
40+
...
41+
}
42+
```
43+
44+
When reviewing: check whether a new symbol should be public. If not, make it `static` and name it with the module's existing vocabulary.
45+
46+
---
47+
48+
## Rule 2 - Use the project's C naming conventions
49+
50+
Use `snake_case` for project-owned functions, variables, fields, files, callbacks, and tasks. Use `*_t` typedef names for structs, enums, and function pointer types where the surrounding code does. Use `ALL_CAPS` for macros and enum values.
51+
52+
Keep Pico SDK, FreeRTOS, lwIP, u8g2, CYW43, and vendor-library names as-is. Do not wrap library types just to make names prettier.
53+
54+
**Wrong:**
55+
```c
56+
typedef struct {
57+
int TimeoutMs;
58+
} TimerConfig;
59+
60+
void StartTimer(TimerConfig *config);
61+
```
62+
63+
**Right:**
64+
```c
65+
typedef struct {
66+
int timeout_ms;
67+
} timer_config_t;
68+
69+
void start_timer(timer_config_t *config);
70+
```
71+
72+
When reviewing: flag CamelCase in project-owned C APIs unless it comes from an external type, constant, generated code, or local legacy API that the edit is intentionally preserving.
73+
74+
---
75+
76+
## Rule 3 - Match formatting in the current file
77+
78+
Use four-space indentation. Most source uses K&R braces for functions and control blocks:
79+
80+
```c
81+
bool motor_config_init(void) {
82+
bool is_ok = true;
83+
84+
if (!is_ok) {
85+
return false;
86+
}
87+
88+
return true;
89+
}
90+
```
91+
92+
Pointer spacing varies in the existing code, with both `char * s` and `char *s` present. Match the surrounding file instead of imposing a repo-wide rewrite. Avoid formatting-only churn outside the edited area.
93+
94+
Keep designated initializers for config/default structures, with short inline comments where they describe hardware, EEPROM layout, units, or user-visible behavior.
95+
96+
**Right:**
97+
```c
98+
const eeprom_wireless_metadata_t default_eeprom_wireless_metadata = {
99+
.wireless_data_rev = 0,
100+
.ssid = "",
101+
.pw = "",
102+
.auth = AUTH_WPA2_MIXED_PSK,
103+
.timeout_ms = 30000, // 30s
104+
.enable = false,
105+
};
106+
```
107+
108+
When reviewing: distinguish style consistency from broad cleanup. Prefer focused fixes over reformatting whole files.
109+
110+
---
111+
112+
## Rule 4 - Use local error and return contracts
113+
114+
This project usually returns `bool` for success/failure and small project enums for richer initialization failures. Preserve that style unless the surrounding module already uses another contract.
115+
116+
Use early returns for invalid state, failed allocation, failed hardware initialization, failed EEPROM reads/writes, and task/queue creation failures. Diagnostics are commonly `printf(...)`; keep messages actionable and include hardware/config context when available.
117+
118+
**Wrong:**
119+
```c
120+
int init_sensor(sensor_t *ctx) {
121+
if (ctx == NULL) {
122+
return -1;
123+
}
124+
125+
return sensor_open(ctx);
126+
}
127+
```
128+
129+
**Right:**
130+
```c
131+
bool sensor_init(sensor_t *ctx) {
132+
if (ctx == NULL) {
133+
printf("Sensor context is NULL\n");
134+
return false;
135+
}
136+
137+
if (!sensor_open(ctx)) {
138+
printf("Unable to initialize sensor\n");
139+
return false;
140+
}
141+
142+
return true;
143+
}
144+
```
145+
146+
For multi-stage hardware initialization, use a module enum when the caller needs to distinguish failures:
147+
148+
```c
149+
typedef enum {
150+
MOTOR_INIT_OK = 0,
151+
MOTOR_INIT_CFG_ERR = 2,
152+
MOTOR_INIT_COARSE_DRV_ERR = 3,
153+
MOTOR_INIT_FINE_DRV_ERR = 4,
154+
MOTOR_INIT_PIO_ERR = 5,
155+
} motor_init_err_t;
156+
```
157+
158+
When reviewing: flag silent failures, ignored allocation/task creation failures in new code, and ad-hoc integer return codes when a local enum or `bool` contract already exists.
159+
160+
---
161+
162+
## Rule 5 - Follow the EEPROM configuration pattern
163+
164+
Persistent configuration lives in EEPROM-backed structs with defaults and CRC validation through shared helpers in `common.c`.
165+
166+
The usual pattern is:
167+
168+
- A persistent config struct in the module header, often named `eeprom_*_data_t` or `eeprom_*_metadata_t`
169+
- A global live module config object in the source file
170+
- A `const ... default_*` initializer with `.revision = 0` or similar legacy revision field
171+
- `*_config_init()` calls `load_config(...)`
172+
- `*_config_save()` calls `save_config(...)`
173+
- Successful init registers the save handler with `eeprom_register_handler(...)`
174+
- REST handlers update live config and save only when the `ee` parameter is true
175+
176+
**Right:**
177+
```c
178+
bool wireless_config_save() {
179+
bool is_ok = save_config(EEPROM_WIRELESS_CONFIG_BASE_ADDR,
180+
&wireless_config.eeprom_wireless_metadata,
181+
sizeof(eeprom_wireless_metadata_t));
182+
return is_ok;
183+
}
184+
```
185+
186+
When writing a new config feature, preserve EEPROM layout compatibility. Add fields carefully, keep defaults explicit, and update the relevant EEPROM address/revision policy only when required.
187+
188+
---
189+
190+
## Rule 6 - Keep target hardware configuration in `targets/`
191+
192+
Board-specific pin and peripheral mappings live under `targets/`, for example `targets/raspberrypi_pico_w_config.h`. Use macros for fixed hardware resources:
193+
194+
```c
195+
#define MOTOR_UART uart1
196+
#define MOTOR_UART_TX 4
197+
#define MOTOR_UART_RX 5
198+
#define MOTOR_PIO pio0
199+
```
200+
201+
Project modules should consume these macros via `configuration.h` rather than hard-coding pins or peripheral instances locally.
202+
203+
When adding RP2040/RP2350 board support, prefer a new target config header and CMake board selection updates over conditional clutter in feature modules.
204+
205+
---
206+
207+
## Rule 7 - Use Pico SDK hardware APIs directly and visibly
208+
209+
Hardware control should be explicit and close to the module that owns it. Prefer Pico SDK calls already used in the codebase:
210+
211+
- GPIO: `gpio_init`, `gpio_set_dir`, `gpio_put`, `gpio_set_function`
212+
- UART: `uart_init`, `uart_write_blocking`, `uart_read_blocking`, `uart_is_readable_within_us`
213+
- PIO: `pio_claim_unused_sm`, `pio_add_program`, `pio_sm_set_enabled`, `pio_sm_put_blocking`
214+
- Clocks/time: `clock_get_hz`, `time_us_32`, `busy_wait_us`, `busy_wait_ms`
215+
- CYW43/lwIP: protect lwIP calls with `cyw43_arch_lwip_begin()` / `cyw43_arch_lwip_end()` where the surrounding code does
216+
217+
Check return values for resource-claiming APIs and report failures:
218+
219+
```c
220+
int sm = pio_claim_unused_sm(pio, true);
221+
if (sm < 0) {
222+
printf("Unable to claim state machine, err: %d\n", sm);
223+
return false;
224+
}
225+
```
226+
227+
When reviewing: flag hard-coded pin numbers, hidden hardware side effects, unchecked PIO/UART resource setup, and lwIP calls that violate the local CYW43 locking pattern.
228+
229+
---
230+
231+
## Rule 8 - Treat FreeRTOS tasks, queues, and semaphores explicitly
232+
233+
Task code favors visible loops, explicit queue commands, and simple state machines. Task functions use `void *p` or `void *args`, cast near the top or at use sites, then loop.
234+
235+
Creation commonly uses `xTaskCreate(...)` with literal task names, stack sizes such as `configMINIMAL_STACK_SIZE` or module-specific values, and explicit priorities. New code should check queue/semaphore allocation and task creation when practical.
236+
237+
**Right:**
238+
```c
239+
wireless_ctrl_queue = xQueueCreate(5, sizeof(wireless_ctrl_t));
240+
if (!wireless_ctrl_queue) {
241+
printf("Unable to create wireless control queue\n");
242+
return;
243+
}
244+
```
245+
246+
Use `vTaskDelayUntil(...)` for periodic loops that should keep cadence, and `vTaskDelay(pdMS_TO_TICKS(...))` for simple sleeps.
247+
248+
When reviewing: flag hidden background behavior, unbounded busy loops after the scheduler starts, queue item-size mismatches, blocking calls in high-priority paths without a clear reason, and missing synchronization around shared display buffers or shared hardware.
249+
250+
---
251+
252+
## Rule 9 - Build u8g2 and MUI views directly
253+
254+
Display code is intentionally imperative: clear the buffer, set font, draw strings/lines/shapes, send the buffer, and delay at the required cadence. Keep layout calculations local and readable.
255+
256+
Use the display access helpers where shared buffer access matters:
257+
258+
- `get_display_handler()`
259+
- `acquire_display_buffer_access()`
260+
- `release_display_buffer_access()`
261+
262+
**Right:**
263+
```c
264+
u8g2_t *display_handler = get_display_handler();
265+
266+
u8g2_ClearBuffer(display_handler);
267+
u8g2_SetFont(display_handler, u8g2_font_helvB08_tr);
268+
u8g2_DrawStr(display_handler, 5, 10, title_string);
269+
u8g2_SendBuffer(display_handler);
270+
```
271+
272+
When reviewing: flag display updates from multiple tasks without a clear locking or ownership story, oversized UI abstractions, and text drawing that can exceed fixed display buffers without bounds checks.
273+
274+
---
275+
276+
## Rule 10 - Keep REST handlers small and static-buffer based
277+
278+
REST endpoint handlers usually accept `struct fs_file *file, int num_params, char *params[], char *values[]`, update live settings from compact parameter names, and return static response buffers with `FS_FILE_FLAGS_HEADER_INCLUDED`.
279+
280+
Prefer bounded formatting:
281+
282+
```c
283+
static char json_buffer[256];
284+
285+
snprintf(json_buffer,
286+
sizeof(json_buffer),
287+
"%s{\"enabled\":%s}",
288+
http_json_header,
289+
boolean_to_string(enabled));
290+
```
291+
292+
Do not expose secrets such as WiFi passwords in responses. Preserve the existing compact parameter mapping style (`w0`, `m1`, `c13`, `ee`) unless the user asks for an API redesign.
293+
294+
When reviewing: flag response buffer overflow risk, returning stack memory through `file->data`, unsanitized secret output, and failing to set `file->len`, `file->index`, and `file->flags`.
295+
296+
---
297+
298+
## Rule 11 - Use memory allocation conservatively
299+
300+
This firmware uses standard C allocation (`malloc`, `free`) in project code and C++ objects in selected C++ modules. Keep allocations bounded, check for `NULL`, and free on every failure path.
301+
302+
Prefer static buffers for small HTTP responses, display strings, and fixed hardware data where the existing module does. Avoid long-lived heap allocations in fast control loops unless there is a clear ownership model.
303+
304+
**Right:**
305+
```c
306+
uint8_t *buf = malloc(write_size);
307+
if (!buf) {
308+
printf("Unable to allocate buffer with size: %d\n", write_size);
309+
return false;
310+
}
311+
312+
...
313+
314+
free(buf);
315+
```
316+
317+
When reviewing: flag unchecked allocation, allocator mismatches, stack buffers that are too large for FreeRTOS task stacks, and heap use inside timing-sensitive loops.
318+
319+
---
320+
321+
## Rule 12 - Comments explain hardware intent, units, and compatibility
322+
323+
Comments should explain hardware intent, timing assumptions, EEPROM compatibility, units, REST parameter mappings, and concurrency assumptions. Keep comments short and practical. Preserve useful `TODO`, `FIXME`, and `NOTE` markers when they describe real incomplete work or constraints.
324+
325+
**Wrong:**
326+
```c
327+
// Set value to true
328+
enabled = true;
329+
```
330+
331+
**Right:**
332+
```c
333+
// At 250k baud rate the transmit time is about 320us. Need to wait long enough to not read the echo message back.
334+
busy_wait_us(20);
335+
```
336+
337+
When reviewing: flag misleading comments and empty narration, but do not demand Doxygen for every internal helper. Public headers may use brief comments where the surrounding header already does.
338+
339+
---
340+
341+
## Rule 13 - Keep C++ as a bridge, not a second architecture
342+
343+
Use C++ where it is already needed for helper classes or modules such as `FloatRingBuffer` and selected mode implementations. Keep public headers C-callable with `extern "C"` guards when C modules need to call them.
344+
345+
**Right:**
346+
```c
347+
#ifdef __cplusplus
348+
extern "C" {
349+
#endif
350+
351+
bool charge_mode_config_init(void);
352+
353+
#ifdef __cplusplus
354+
}
355+
#endif
356+
```
357+
358+
Avoid introducing classes, templates, exceptions, RTTI-heavy patterns, STL containers, or broad C++ architecture into firmware modules unless the surrounding module already depends on them and the user explicitly wants that direction.
359+
360+
When reviewing: flag C++ abstractions that make firmware control flow harder to inspect or call from C.
361+
362+
---
363+
364+
## Rule 14 - Keep CMake changes local and Pico SDK-native
365+
366+
Top-level CMake uses Pico SDK initialization, FreeRTOS import, u8g2, Trinamic interface sources, and `src/CMakeLists.txt` for generated/support targets. Preserve this structure.
367+
368+
Prefer:
369+
370+
- `target_sources(...)` for explicit generated or library-owned source additions
371+
- `target_link_libraries(...)` for Pico SDK hardware libraries such as `hardware_pio`, `hardware_spi`, `hardware_i2c`, `hardware_pwm`
372+
- target config include paths through `PICO_BOARD_HEADER_DIRS` and `targets/`
373+
374+
Do not vendor new dependencies into `library/` or alter SDK import behavior unless the user asks for that scope.
375+
376+
---
377+
378+
## Review Workflow
379+
380+
When asked to review C/C++ code:
381+
382+
1. Read the file or diff in full, plus the nearby header/source pair when relevant.
383+
2. Prioritize behavioral risks first: hardware resource claims, task/queue/semaphore safety, display buffer concurrency, EEPROM compatibility, REST buffer lifetime/size, PIO/UART timing, CYW43/lwIP locking, and memory ownership.
384+
3. Then list style violations against these rules with file and line references.
385+
4. Suggest the smallest correction that matches the surrounding module.
386+
5. If no issues are found, say so and mention any residual test or hardware-verification gap.
387+
388+
When writing C/C++ code:
389+
390+
Apply these rules before editing. Match the current file's local conventions, keep edits scoped, and prefer existing helpers and patterns over new abstractions.

0 commit comments

Comments
 (0)