Integrace bez RTOS: XCP on TCP z hlavní smyčky
Příklad examples/nucleo_h723zg_baremetal je tatáž deska a tentýž
obsah ukázky jako examples/nucleo_h723zg, přeložený bez FreeRTOS a
bez socketů: lwIP běží v režimu NO_SYS a XCP server tvoří šablona
glue glue/baremetal_lwip_raw/xcp_tcp_raw_glue.c, řízená z hlavní
smyčky. Vše v této kapitole je to, co se mění oproti kapitole
Integrace: XCP on TCP; engine,
kontrakt enginu, pravidla pro
proměnné a nástroje pro adresy
jsou stejné.
Stejné a odlišné
| Stejné jako s FreeRTOS | Odlišné bez RTOS |
|---|---|
zdroje enginu, qfw_xcp.h, cmake/qfw_xcp.cmake, QFW_XCP_TRANSPORT=eth | glue: glue/baremetal_lwip_raw/xcp_tcp_raw_glue.c místo glue/freertos_lwip/xcp_sdk_glue.c — lwIP RAW (callback) API, žádná úloha, žádný socket |
| port hodin a kontrakt hodin | API glue: QfwXcpTcpRawGlue_Init(&config) jednou a navíc QfwXcpTcpRawGlue_Poll() v každém průchodu hlavní smyčky (poll nahrazuje server task) |
identita, úroveň logu a port v konfigurační struktuře (tQfwXcpTcpRawGlueConfig má stejných pět polí jako tQfwXcpGlueConfig) | model kontextů: transport v hlavní smyčce, spouštění DAQ z přerušení — glue dává enginu kritickou sekci PRIMASK (viz níže) |
qfw_xcp_create_event / qfw_xcp_trigger_event, daqId v QInsight | kde volání sídlí: main.c a stm32h7xx_it.c (sekce USER CODE) místo freertos.c; DAQ event se spouští z přerušení SysTick |
opravy lwIP projektu STM32H7 (linker skript, ethernetif.c, viz Řešení potíží) | volby lwIP: NO_SYS 1, žádné volby socketů, ale TCP_MSS / TCP_SND_BUF musí být zvětšeny (viz níže) |
CubeMX: lwIP bez RTOS
Kde: váš soubor .ioc a LWIP/Target/lwipopts.h. Co se liší
od projektu s FreeRTOS:
- Middleware → FREERTOS: nezapnuto. SYS → Timebase source:
SysTick (
HAL_GetTickpohání časovače lwIP přessys_nowi plánovač ukázky). - Connectivity → ETH: jako u projektu s FreeRTOS. Priorita přerušení
ETH je volná (bez omezení syscall priority FreeRTOS); v ovladači CubeMX
bez RTOS ISR pouze maže příznaky DMA — rámce čte
MX_LWIP_Process()z hlavní smyčky. Zachovejte to (viz model kontextů níže). - Middleware → LWIP: CubeMX bez zapnutého RTOS generuje
NO_SYS 1,LWIP_NETCONN 0,LWIP_SOCKET 0automaticky. DHCP vypnuto, statická IP jako u FreeRTOS;MEM_SIZE16384 aLWIP_RAM_HEAP_POINTERjako v příkladu (do heapu lwIP kopírujetcp_writesegmenty XCP). USER CODE BEGIN 1vlwipopts.h— volby, na kterých glue závisí (glue je kontroluje při překladu:#error/_Static_assert):Volba Hodnota Proč LWIP_TCP_KEEPALIVE1 keepalive per spojení (2 s klidu, 3 sondy po 1 s): master, který zmizel za vlastní linkou desky, je odhalen do ~5 s a port je opět volný (kabel vytažený u desky: link callback, viz Spojení níže) LWIP_NETIF_TX_SINGLE_PBUF1 jeden pbufna odesílaný segment — ovladač STM32H7 má jen 4 TX deskriptoryTCP_MSS1460 maximum Ethernetu (1500 − 40). S výchozí hodnotou lwIP 536 by se každý plný segment enginu dělil na tři TCP pakety TCP_SND_BUF4 * TCP_MSS(5840)povinné. Glue zařadí celý segment enginu (až 1480 B) jedním tcp_write, nebo vůbec; výchozí hodnota lwIP (2 × 536 = 1072) by plný segment nepřijala nikdy a engine by zůstal trvale v backpressure. Čtyři segmenty v letu jsou dobrý výchozí stav
Všechny čtyři jsou chráněné#ifndefa nejsou v.ioc— GUI CubeMX dál ukazuje výchozí hodnoty; blokUSER CODEregeneraci přežije. Hodnoty, které CubeMX generuje pro výchozí MSS (TCP_SND_QUEUELEN 9,TCP_SNDLOWAT 1071, …), s tímto nastavením kontrolami lwIP stále procházejí (TCP_SND_QUEUELEN≥ 2 ×TCP_SND_BUF/TCP_MSS= 8). Proč celé segmenty: engine zahazuje zařazené segmenty přiDISCONNECT, při novémCONNECTa při resetu transportu; napůl zapsaný segment by rozsynchronizoval proud bajtů. Glue pro FreeRTOS si částečný zápis může dovolit, protože ve své úloze může čekat; hlavní smyčka nemůže.- Generate code, poté proveďte opravy pro STM32H7 přesně jako
u projektu s FreeRTOS (linker skript,
ETH_RX_BUFFER_CNT, ochranaHAL_ETH_RxLinkCallback—REGEN-NOTES.mdpříkladu bez RTOS).
CMake
Kde: váš kořenový CMakeLists.txt. Jak (CMakeLists.txt
příkladu bez RTOS, liší se jen cesta k SDK):
include(${SDK}/cmake/qfw_xcp.cmake) # transport eth (default)
add_library(qfw_example STATIC
app/xcp_demo.c # your application: event + cyclic work
app/demo_map.c
app/demo_signals.cpp
${QFW_XCP_GLUE_BAREMETAL_LWIP_RAW_SOURCES} # glue/baremetal_lwip_raw/xcp_tcp_raw_glue.c
${QFW_XCP_PORT_CLOCK_SOURCES} # port/clock/clock64.c
)
target_include_directories(qfw_example PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/app
${QFW_XCP_GLUE_BAREMETAL_LWIP_RAW_INCLUDE_DIRS}
${QFW_XCP_PORT_CLOCK_INCLUDE_DIRS}
)
target_compile_definitions(qfw_example PUBLIC CLOCK_TICKS_PER_S=550000000UL) # YOUR CPU clock
target_link_libraries(qfw_example PUBLIC qfw::xcp PRIVATE stm32cubemx)
target_link_libraries(${CMAKE_PROJECT_NAME} stm32cubemx qfw_example)
Glue potřebuje hlavičky lwIP vašeho projektu (lwip/tcp.h,
lwip/pbuf.h, váš lwipopts.h), proto sedí ve vašem cíli a linkuje
stm32cubemx privátně — přesně jako glue pro FreeRTOS.
main.c: jednou Init, pak stále Poll
Kde: Core/Src/main.c, pouze sekce USER CODE. Co: callback
hodin, konfigurace glue, dvě inicializační volání a tělo smyčky.
Jak (main.c příkladu):
/* USER CODE BEGIN Includes */
#include "clock64.h"
#include "qfw_xcp.h"
#include "xcp_tcp_raw_glue.h"
#include "xcp_demo.h"
/* USER CODE END Includes */
/* USER CODE BEGIN 0 */
static uint64_t XcpClockUs(void) /* engine clock contract: monotonic 64-bit microseconds */
{
return Clock64_Get();
}
/* USER CODE END 0 */
int main(void)
{
...
MX_GPIO_Init();
MX_LWIP_Init(); /* generated: lwIP is up after this line (NO_SYS: no thread is started) */
/* USER CODE BEGIN 2 */
Clock64_Init();
/* USER CODE END 2 */
... BSP LEDs, COM1 (printf) ...
/* USER CODE BEGIN WHILE */
tQfwXcpTcpRawGlueConfig xcpConfig =
{
.project_name = "baremetal", /* shown to the master, <= 31 chars */
.epk = "V100", /* software version id */
.port = 5555U,
.log_level = 2U, /* 1 error, 2 warning, 3 info (adds connection messages) */
.get_clock_us = XcpClockUs,
};
if (!QfwXcpTcpRawGlue_Init(&xcpConfig)) /* engine init + listening pcb */
{
Error_Handler();
}
if (!QfwXcpDemo_Init()) /* qfw_xcp_create_event("demo10ms", 10000) -> id 0, maps */
{
Error_Handler();
}
while (1)
{
MX_LWIP_Process(); /* ETH RX + lwIP timers; the glue's TCP callbacks run in here -> qfw_xcp_rx */
Clock64_Update(); /* single writer of the 64-bit clock, must run well below every 7.8 s */
QfwXcpTcpRawGlue_Poll(); /* qfw_xcp_poll -> transmit callback -> tcp_write, then one tcp_output */
QfwXcpDemo_Poll(); /* every 500 ms: map tick (the 10 ms signal step + DAQ event runs in SysTick, see below) */
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Proč toto pořadí: QfwXcpTcpRawGlue_Init musí běžet až po
MX_LWIP_Init() (vytváří naslouchající pcb) a — chcete-li vidět jeho
úvodní hlášku — až po inicializaci COM portu; proto je v sekci
USER CODE WHILE těsně před smyčkou, ne v USER CODE 2. Uvnitř
smyčky je MX_LWIP_Process první (přijaté příkazy se vykonají a jejich
odpovědi zařadí do fronty), poll je v témže průchodu odešle.
QfwXcpDemo_Poll (app/xcp_demo.c) je cyklická práce aplikace:
plánuje podle HAL_GetTick s termíny odolnými proti přetečení, takže
opožděný průchod nedohání dávkou volání — vzor DemoDue převezměte pro
vlastní cykly ve smyčce. Rychlý cyklus (krok signálů + DAQ event každých
10 ms) běží v QfwXcpDemo_Tick1ms z přerušení SysTick (USER CODE SysTick_IRQn 1 v stm32h7xx_it.c), viz model kontextů níže. K integraci
patří ještě dvě sekce USER CODE:
/* Core/Src/stm32h7xx_it.c */
/* USER CODE BEGIN Includes */
#include "xcp_demo.h"
/* USER CODE END Includes */
void SysTick_Handler(void)
{
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
QfwXcpDemo_Tick1ms(); /* every 10 ms: generators + qfw_xcp_trigger_event, in interrupt context */
/* USER CODE END SysTick_IRQn 1 */
}
/* LWIP/App/lwip.c - stav linky do glue */
/* USER CODE BEGIN 0 */
#include "xcp_tcp_raw_glue.h"
/* USER CODE END 0 */
static void ethernet_link_status_updated(struct netif *netif)
{
if (netif_is_up(netif)) {
/* USER CODE BEGIN 5 */
QfwXcpTcpRawGlue_OnLinkChange(true);
/* USER CODE END 5 */
} else {
/* USER CODE BEGIN 6 */
QfwXcpTcpRawGlue_OnLinkChange(false); /* master je nedosažitelný: spojení zahodit hned */
/* USER CODE END 6 */
}
}
Model kontextů, kritická sekce a přerušení
Transport běží v jednom kontextu: v hlavní smyčce. lwIP v režimu
NO_SYS běží jen uvnitř MX_LWIP_Process(), takže callback recv glue
(→ qfw_xcp_rx) a QfwXcpTcpRawGlue_Poll (→ qfw_xcp_poll) jej sdílejí
a pravidlo
kontraktu enginu
je splněno samotnou konstrukcí.
Měření přichází z druhého kontextu. Glue registruje kritickou sekci
PRIMASK (uložit PRIMASK, zakázat přerušení, obnovit při vnějším
opuštění, s čítačem zanoření, protože engine sekci vnořuje — viz
Kritická sekce).
Díky ní smí být qfw_xcp_trigger_event volán z přerušení — příklad
spouští svůj event demo10ms ze SysTick_Handler (QfwXcpDemo_Tick1ms,
každých 10 ms), tak jako by to dělala regulační smyčka v časovači: vzorky
DAQ a zápisy STIM tohoto eventu proběhnou uvnitř ISR, synchronně
s cyklem, a perioda vzorkování nezávisí na tom, jak dlouho trvá průchod
hlavní smyčky. ISR jen kopíruje do fronty enginu; bajty odejdou z desky
v příštím QfwXcpTcpRawGlue_Poll.
Důsledky, které musíte zachovat:
- Z přerušení jen
qfw_xcp_trigger_event.qfw_xcp_rx,qfw_xcp_polla každé další volání enginu zůstávají v hlavní smyčce. - Z přerušení nic z lwIP. Ovladač CubeMX bez RTOS se tak chová už
sám (ISR ETH pouze maže příznaky); nepřidávejte
ethernetif_inputani volání lwIP doHAL_ETH_RxCpltCallback. - Práci v ISR držte krátkou. Sekce zakáže všechna přerušení na dobu režie fronty a kopie DAQ jednoho eventu (mikrosekundy); trigger velkého seznamu eventu v rychlém ISR o tolik zvýší latenci přerušení všem ostatním.
- Log z ISR je potlačen. Hláška enginu vzniklá uvnitř triggeru se
počítá, netiskne (
printfblokuje a běžel by se zakázanými přerušeními); počet ohlásí příštíPolljako[XCP W] n engine log line(s) raised in interrupt context not printed. Při měření se tento řádek nesmí objevit. - Glue je napsaná pro Cortex-M (
__get_PRIMASK/__disable_irq/__set_PRIMASKz CMSIS). Na jiném jádru obě funkce nahraďte ekvivalentní globální maskou přerušení se stejným chováním zanoření. - Průchod smyčky je kadence pollu. Dlouhá práce v jednom průchodu
zdržuje odpovědi i vysílání DAQ:
printfpřilog_level3 stojí ≈ 4 ms na řádek na virtuálním COM portu ST-LINK aHAL_ETH_Transmitovladače CubeMX bez RTOS blokuje, dokud DMA rámec neodešle (≈ 0,12 ms na plný rámec při 100 Mbit/s).
Spojení: co glue dělá a co uvidíte
Politika je stejná jako u glue pro FreeRTOS, provedená nad RAW API:
| Situace | Co se stane |
|---|---|
| master se připojí | callback accept: nastaví se keepalive (2 s / 1 s / 3), qfw_xcp_on_transport_reset(), pcb se stane jediným spojením; na úrovni 3 [XCP I] master connected from ip:port |
| druhý klient se připojí, zatímco je master připojen | odmítnut okamžitě pomocí RST (tcp_abort v accept); běžící relace není narušena. [XCP W] refused a second connection from ip:port (master already connected). Druhý QInsight ukazuje jednou za sekundu connected → forcibly closed a připojí se, jakmile první relace skončí |
| master zavře (FIN) nebo spadne (RST) | glue spojení zruší (RST) — každé zavření je abort, takže pcb i všechny zařazené segmenty se uvolní okamžitě; reset enginu; zpět do čekání na mastera |
| master tiše zmizí za linkou desky (kabel vytažený u switche, vypnuté nebo zamrzlé PC) | keepalive spojení zruší ~5 s po posledním přijatém segmentu; do té doby je znovu se připojující master odmítán jako druhý klient — driver QInsight zkouší každou sekundu a po vypršení se připojí |
proud bajtů se rozsynchronizuje (qfw_xcp_rx vrátí false) | spojení je zrušeno (RST), [XCP E] XCP stream desynchronized |
vysílací fronta nebo heap lwIP jsou plné (tcp_write → ERR_MEM) | backpressure: callback transmit vrátí false, engine si segment ponechá a při příštím pollu nabídne tytéž bajty; qfw_xcp_is_backpressured() je mezitím pravdivé. Master, který žije, ale přestal číst, drží glue v backpressure, dokud se nepřipojí znovu |
| výpadek linky u desky (kabel vytažený z desky) | link callback (ethernet_link_status_updated v lwip.c → QfwXcpTcpRawGlue_OnLinkChange(false)) přiměje glue spojení s masterem zahodit v příštím Poll (na úrovni 3 [XCP I] connection closed (link down), waiting for a master). Po návratu linky je nové připojení mastera přijato okamžitě; s connectionTimeoutMs 1500 v QInsight je měření zpět zhruba 3,5 s po zasunutí kabelu (vyjednání linky plus jeden pokus, který vypršel, protože začal příliš brzy) |
Nagleův algoritmus je zapnutý (výchozí chování TCP, stejně jako v glue
pro FreeRTOS): malý paket může čekat na zpožděný ACK mastera (≈ 200 ms
ve Windows) — časové značky DAQ to neovlivní, jen příchod pomalých
proudů je dávkovaný. Přepínač GLUE_TCP_NODELAY na začátku glue
přepne na TCP_NODELAY.
Ukázkový projekt QInsight pro tento obraz
(Qenex_TcpClient_XCP_NucleoH723_Maps_BareMetal.qproj) popisuje
kapitola Kalibrační mapy po XCP on TCP.
Dále
Kontrakt enginu — pravidla, která za glue stojí; Řešení potíží — řádky pro integraci bez RTOS v tabulce příznaků.

