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

FilePurpose
ui_screens.c / .hScreen and widget construction — creates every LVGL object, sets properties, styles, flags, states, events, and animations. Entry point: ui_init().
assets.c / .hFonts and images compiled to static C-arrays with LVGL descriptors. No filesystem needed.
pli_can.c / .hCAN/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 / .hYour 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 / .hGenerated node/logic state and evaluation — comparisons, math, delays, watchdogs, screen switches, animation triggers, cyclic timers, NVM nodes.
pli_timers.c / .hLVGL 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 / .hOptional — label text formatting helpers (prefix/postfix, decimals, factor). Only emitted if your project uses formatted labels.
pli_dpl.c / .hOptional — 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

A Protok project (.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.

c · ui_screens.c (excerpt)
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). The pli_can_lvgl_worker timer (5 ms) drains the queue, unpacks signals, and writes physical values into bound widgets and logic node inputs.
  • TX — when a CAN_TX block or widget binding needs to send, the generated code packs the frame from current widget/logic values and calls pli_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.

c · pli_timers.c (excerpt)
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

DPL is available only when targeting Protok HMI hardware. It is disabled by default and the files are not emitted unless you opt in.

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