Reference·01
Generated code
The C99 files Protok Studio emits for an HMI/LVGL project, what each one does, and how they fit into your firmware build.
When you click Generate C99, Protok Studio writes a set of portable C99 source and header files into your project's output folder. No proprietary binary blob — everything is readable C that compiles on any C99 toolchain. The files split into two groups: the UI layer (LVGL screens and assets) and the logic layer (CAN processing, signal binding, nodes, HAL).
The file set
| File | Purpose |
|---|---|
ui_screens.c / .h | Screen and widget construction — creates every LVGL object, sets properties, styles, flags, states, events, and animations. Entry point: ui_init(). |
assets.c / .h | Fonts and images compiled to static C-arrays with LVGL descriptors. No filesystem needed. |
pli_can.c / .h | CAN/J1939 processing: frame ring buffer, signal decoding (bit position, factor, offset from your DBC), TX packing. Exposes pli_can_sae1939_process_frame (RX entry) and pli_can_init. |
pli_hal.c / .h | Your porting surface. Four weak stubs: pli_get_tick, pli_can_send_message, pli_nvm_read, pli_nvm_write. Override the ones your hardware needs. See Runtime & HAL. |
pli_nodes.c / .h | Generated node/logic state and evaluation — comparisons, math, delays, watchdogs, screen switches, animation triggers, cyclic timers, NVM nodes. |
pli_timers.c / .h | LVGL timer registration. pli_can_init() creates the 5/20/50/100 ms timers that drive CAN RX draining, node evaluation, and bound-widget updates. |
pli_text_helpers.c / .h | Optional — label text formatting helpers (prefix/postfix, decimals, factor). Only emitted if your project uses formatted labels. |
pli_dpl.c / .h | Optional — Deep Protok Logs (DPL). Snapshots UI state (active screen, widget values, variables) and CAN frames to a compact binary log for in-IDE playback. Protok HMI (PHMI) exclusive. Only emitted if you enable DPL in Project Settings. |
One project, one file set
.lowcode) holds screens, widgets, signal bindings, and low-code logic nodes together. The generator emits the full file set above from that one project — no separate logic project to manage.UI screens
ui_screens.c is the display entry point. ui_init() constructs every screen and widget, applies styles, registers events, and wires up animations using standard LVGL 9.6 APIs.
void ui_init(void) {
ui_screen_main_init();
lv_scr_load(ui_screen_main);
}
void ui_screen_main_init(void) {
ui_screen_main = lv_obj_create(NULL);
ui_btn_start = lv_button_create(ui_screen_main);
lv_obj_set_pos(ui_btn_start, 20, 20);
lv_obj_set_size(ui_btn_start, 120, 48);
lv_obj_add_event_cb(ui_btn_start, on_start_click, LV_EVENT_CLICKED, NULL);
/* ...styles, flags, states, animations... */
}CAN processing
pli_can.c holds the ring buffer and signal decode logic generated from your DBC. Two sides:
- RX — you call
pli_can_sae1939_process_frame(bus, id, data, dlc)from your CAN driver. It enqueues the frame (ISR-safe). Thepli_can_lvgl_workertimer (5 ms) drains the queue, unpacks signals, and writes physical values into bound widgets and logic node inputs. - TX — when a
CAN_TXblock or widget binding needs to send, the generated code packs the frame from current widget/logic values and callspli_can_send_message(your HAL function) to push it to the bus.
Nodes & logic
pli_nodes.c contains the evaluation functions for every logic node in your project: comparisons (IF, OR, AND), math operators, delays, watchdogs, screen switches, animation triggers, cyclic timers, and NVM read/write. These run on their own LVGL timers (20–50 ms) registered bypli_timers.c.
Timer registration
pli_can_init() calls pli_timers_can_init(), which creates only the timers your project actually needs. A project with no logic nodes skips the 20 ms timer; a project with no watchdogs skips that 50 ms timer. Nothing runs that you do not use.
void pli_timers_can_init(void) {
lv_timer_create(pli_can_lvgl_worker, 5, NULL); /* always: CAN RX drain */
lv_timer_create(pli_update_nodes, 20, NULL); /* if logic nodes exist */
lv_timer_create(pli_ui_update_logic_targets, 50, NULL); /* if comparisons */
lv_timer_create(pli_ui_update_bound_objects, 100, NULL); /* if bindings */
/* ...cyclic timers created from your project... */
}
void pli_can_init(void) {
pli_timers_can_init();
}Deep Protok Logs (DPL) — optional
DPL is a Protok HMI (PHMI) exclusive feature. When you enable “Use DPL” in Project Settings, the generator adds a purely additive pli_dpl.c / .h module that records the live state of your HMI — active screen, widget values, variables, and CAN frames — into a compact binary log. You later load that log back in Protok Studio and replay the exact session: screens switch, widgets update, and CAN traffic flows exactly as it did on the device.
The module needs one HAL hook from you — pli_dpl_write(data, len) — that writes the binary stream to whatever sink your hardware provides (UART, file, flash partition). You also call pli_dpl_init() once at startup andpli_dpl_log_can_frame() from your CAN receive path. SeeRuntime & HAL for the hook.
PHMI exclusive
Properties of the output
- Portable C99 — no compiler-specific extensions in the hot path.
- Zero dynamic allocation — all state is static; safe for hard real-time.
- Weak HAL stubs — links and runs immediately; you override only what you need.
- Readable — audit, diff, and version-control it like any source.
- Royalty-free — the generated code is yours.
What's next
- How to port it: Runtime & HAL.
- Pick a target: Hardware targets.