| name | nucleo-native-app |
| description | Scaffold and implement a NucleoOS native firmware app (C++) correctly — anti-flicker, poll handler, RAM discipline, input routing, registration. Use when creating or fixing a native app in firmware/components/nucleo_app/. |
App nativa NucleoOS — guida ottimizzata
Un'app nativa è un app_<id>.cpp registrato in nucleo_app.cpp + CMakeLists.txt.
Schermo 240×135, ~18 KB heap libero frammentato, no PSRAM.
1. Struttura minima
#include "nucleo_app.h"
#include "nucleo_kbd.h"
#include "launcher_theme.h"
#include "app_gfx.h"
static bool s_dirty = true;
static void on_enter(void) {
nucleo_app_set_hint("TAB tab ESC esci");
nucleo_app_request_draw();
}
static void on_key(int key, char ch) { nucleo_app_request_draw(); }
static void on_tick(void) {}
static void on_draw(void) {
if (!s_dirty) return;
s_dirty = false;
}
static void on_exit(void) {}
extern "C" void nucleo_register_<id>(void) {
static const nucleo_app_def_t app = {
"<id>", "<Nome>", "<Categoria>", "<desc>",
'<I>', COLOR,
on_enter, on_key, on_tick, on_draw, on_exit
};
nucleo_app_register(&app);
}
2. Anti-flicker — la regola principale
MAI fare d.fillScreen() + draw in modalità direct_draw=true: scrive al TFT in due passate
e produce sfarfallio evidente. Scegli una sola tecnica:
Tecnica 1 — Canvas + blit (DEFAULT, quasi sempre)
Il run loop blitta il canvas al TFT una volta sola dopo ogni on_draw().
poll_fn() → true → on_draw() disegna sul canvas → framework blitta → TFT aggiornato atomicamente
- Usa
d.fillScreen(), d.fillRect(), etc. liberamente: scrivono in SRAM (veloce).
- Il TFT vede un frame completo, mai uno stato intermedio.
set_direct_draw NON va usato (default false = canvas mode).
- Per animazioni: registra
nucleo_app_set_poll_handler(poll_fn) che restituisce true
alla frequenza target (vedi §3).
static bool poll_fn(void) {
int64_t now = esp_timer_get_time();
if (now - s_last_us < TARGET_INTERVAL_US) return false;
s_last_us = now;
return true;
}
static void on_draw(void) {
d.fillScreen(0x0000);
draw_stuff();
}
Tecnica 2 — Direct draw (solo per app che liberano il canvas)
Solo quando l'app libera i 32 KB del canvas (es. ANIMA per il suo indice L1):
nucleo_app_release_buffers() → libera canvas
nucleo_app_set_direct_draw(true) → il framework non riprende il canvas
on_draw() deve pulire solo le aree modificate (self-clear delle regioni cambiate).
- MAI fillScreen: scriverebbe direttamente al TFT producendo un flash nero visibile.
static void on_draw(void) {
d.fillRect(old_x, old_y, W, H_elem, BG);
draw_element(new_x, new_y);
old_x = new_x; old_y = new_y;
}
3. Poll handler vs on_tick
| on_tick | poll_handler |
|---|
| Frequenza | ~5 Hz (200 ms) | ~50 Hz loop, blitta alla rate che decidi |
| Uso | Aggiornamenti lenti (orologio, stato) | Animazioni ≥ 10 fps |
| Come | Chiama nucleo_app_request_draw() quando cambia | Restituisce true quando il frame è pronto |
| Anti-flicker | OK per UI statiche | Necessario per moto fluido |
#define FPS30_US 33333
static int64_t s_last_us;
static bool poll_fn(void) {
if (!s_animating) return false;
int64_t now = esp_timer_get_time();
if (now - s_last_us < FPS30_US) return false;
s_last_us = now;
return true;
}
nucleo_app_set_poll_handler(poll_fn);
4. Fullscreen
nucleo_app_set_fullscreen(true);
nucleo_app_set_fullscreen(false);
5. RAM discipline — zero heap nelle app
static uint8_t fire_buf[FH][FW];
static uint16_t palette[256];
uint8_t *buf = (uint8_t*)malloc(FH * FW);
Se l'app ha bisogno di RAM contigua grande (SSH, ANIMA, video): usa exclusive_flags:
#include "nucleo_exclusive.h"
static const nucleo_app_def_t app = {
"id", "Nome", ..., on_enter, on_key, on_tick, on_draw, on_exit,
NX_NET_APP
};
6. Input routing — gotcha
static bool on_back(int key) {
if (s_in_submenu) { go_up(); return true; }
return false;
}
static void on_tab(void) { s_tab = (s_tab + 1) % NTABS; nucleo_app_request_draw(); }
nucleo_app_set_back_handler(on_back);
nucleo_app_set_tab_handler(on_tab);
Key constants in nucleo_kbd.h: NK_UP NK_DOWN NK_LEFT NK_RIGHT NK_ENTER NK_BACK NK_TAB NK_NONE.
7. Persistenza settings
#define CFG "/sd/system/config/<id>.json"
static void load(void) {
FILE *f = fopen(CFG, "rb"); if (!f) return;
char buf[128]; int n = fread(buf,1,sizeof(buf)-1,f); fclose(f); buf[n]=0;
int val = 0;
sscanf(buf, "{\"key\":%d}", &val);
s_val = val;
}
static void save(void) {
mkdir("/sd/system", 0775); mkdir("/sd/system/config", 0775);
FILE *f = fopen(CFG, "wb"); if (!f) return;
fprintf(f, "{\"key\":%d}\n", s_val); fclose(f);
}
8. Registrazione
nucleo_app.cpp — aggiungi vicino agli altri extern:
extern "C" void nucleo_register_<id>(void);
e nella nucleo_app_register_builtins():
nucleo_register_<id>();
CMakeLists.txt — aggiungi nella lista SRCS:
"app_<id>.cpp"
9. Checklist prima del build