FULL-C: tips and tricks

Tato stránka popisuje programovací jazyk FULL-C, který je dostupný na vybraných zařízeních SDS. 
Některá zařízení používají SDS-C, pro který máme návody jinde na této WiKi.

Důležité poznámky k práci s FULL-C

Zde jsou neseřazené tipy a triky, tak, vytvořené tak, jak jak postupem času dostáváme různé dotazy od uživatelů.


  • Dbejte na to, aby verze FC byla stejná jak pro překladač (FULLC.EXE) tak i pro vaše SDS. Pokud má vaše SDS starší firmware (a tedy starší verzi FC, například 0x06), zatímco překladač je novější (například FULLC.exe s verzí FC 0x08), tak SDS odmítne novější FC program spustit. Toto se řeší velmi snadno, a to aktualizací firmware ve vašem SDS.


  • I když proběhne překlad ve FULLC.EXE v pořádku, stále může váš FULL-C program obsahovat chyby - to se dozvíte teprve z konzole (web zařízení SDS: typicky 192.168.1.250/echo.htm), proto ji vždy průběžně kontrolujte !


  • Pozor na signed a unsigned typy a jejich používání. FULL-C se chová dle standardu C, a detaily standardu vás mohou zmást.

Např. char je signed ! (pokud jej explicitně nedefinujeme jako unsigned char).

Příklad - chceme vypsat HEX kód pro jednobajtový znak.

Toto ale neudělá to co "chceme": vypíše to FFFFFFDB - přesně podle specifikace jazyka C, kdy dojde nejprve k převodu na int32

char ch = 0xDB;
printf("%x", ch);

Příklad správného zápisu: vypíše to DB tak jak chceme, a to proto že jsme správně uvedli že to chceme zpracovat jako unsigned char.

char ch = 0xDB;
printf("%x", (unsigned char)ch);

Další příklad velkého problému je následující kus programu pro výpis obsahu textového pole jako HEX znaků:

char pole[32];
char * ch;

ch = (char *)&pole;
for (i = 0; i < 32; i++)
{
  printf("%02x", *ch++); // <<<<<<< WARNING !!!! *ch is signed char !!!
}

Opět, správné řešení je ve správném přetypování:

 printf("%02x", (unsigned char)*ch++); 


  • Před provedením jakékoliv síťové komunikace ve FULL-C (např. odeslání emailu), vždy zkontrolujte stav, a to pomocí: status = SDS_get_u(27); (když je status==0 tak SDS nemá připojení - nezačínejte komunikaci, čekejte a testuje stav).


  • Pole pro uchování textů můžete přímo deklarovat, např. na zásobníku (stack) jako char text[128]; - nebo na haldě (heap), pro což využijte text = (char *)malloc(128); a free(text);text=0; funkce. Výběr z těchto možností je na vás (malé pole / malé texty jdou dát na zásobník, velké už patří do haldy).


  • Využijte 64-bitových proměnných (int64_t nebo uint64_t) tam, kde vám 32 bitů nestačí. Například pro počítadla, nebo výpočty s velkými čísly (nebo jen stačí že by měl být velký mezivýsledek a při použití 32-bit čísel by jste o něj "přišli").


  • Pro zjištění, jaký největší blok paměti (heap) lze v danou chvíli alokovat pomocí malloc() funkce, využijte funkci SDS_heap_stats() . Ačkoliv může být celková hodnota volné heap pamět velká, toto celkové volné místo je vždy roztroušeno na různé malé oddělené bloky (fragmentace) - a vždy lze alokovat jen po jednom bloku, a jeho největší možná velikost je zjistitelná právě ze zmíněné funkce.


  • Vždy když uvolníte paměť, nezapomeňte ihned ručně vynulovat daný uvolněný ukazatel. Zabrání to chybám ve vašem programu. Příklad:
// definice ukazatale
void *ptr; 

...
// alokujeme si pamet, pro priklad 32 bajtu
ptr = malloc(32);

// pamet na kterou ukazuje ptr lze ted pouzivat (pozor at nepristupujete mimo alokovany rozsah)
...

// uz pamet nepotrebujeme, tak ji uvolnime:
free(ptr); 
ptr = 0;   // vzdy ihned za zavolanim free() nastavte ptr do nuly !!!

...


  • Funkce printf() a puts() a putchar() zapisují do konzole (web zařízení SDS, typicky: 192.168.1.250/echo.htm), čehož lze využít pro ladění programu, nebo pro dálkový monitoring zařízení. Nicméně pro průběžné spolehlivé získávání hodnot a údajů z SDS nebo vašeho programu, je vhodně použít systém sdílených proměnných, nebo SNMP.


  • FULL-C používá právě přesně 32-bitů pro datový typ float. Počítejte s touto informací pro správné pochopení přesnosti práce s desetinnými čísly (omezené rozlišení dané 32bity). Pro většinu aplikací toto nepředstavuje problém, pokud ale chcete provádět složité výpočty s desetinnými čísly, je potřeba znát tato omezení. Přesné vysvětlení viz např. "Single-precision floating-point format".


  • Funkce printf() a sprintf() umí zpracovat float proměnné (%f), ale neumí je uživatelský formátovat (neumí např. %0.2f - ale umí jen základní %f, to znamená že vypíše úplně celou délku čísla se všemi pozicemi za tečkou, což může být i dost dlouhý text). Toto je aktualizováno v nové verzi FULL-C (tam už to jde).


  • Nepoužívejte prázdný string (tj. "").

Tři příklady zakázaného kódu pro FULL-C:

// do not do any of the following:
//
char text1[10] = "";  // spatne !
//
strncpy(text2,"",10); // spatne !
//
sprintf(text2,"");    // spatne !


  • Před použitím některé z funkcí, které přistupují k serveru na Internetu, je vhodné provést volání dns_resolv() pro zjištění aktuální IP adresy (IP0.IP1.IP2.IP3) daného serveru, vždy před zavoláním konkrétní funkce (např. http_get() ). Určitě není vhodné používat přímo natvrdo zapsané číselné adresy (ty se mohou změnit např. při přesunu serveru).


  • Pokud některá z FULL-C funkcí selže (např. smtp_send() ohlásí přes smtp_send_status() chybu), tak je vhodné mít FULL-C program napsán tak, aby chvíli počkal, a pak to celé zkusil znovu. Při více selháních je pak potřeba provést další akci (oznámit chybu, zjistit stav připojení k síti, atd.).


  • Přes FULL-C lze pouze číst kalibrační konstanty pro analogové vstupy. Pokud je chcete hromadně nastavit, použijte s výhodou 192.168.1.250/cgi_cfg_adc?xxxx volání (uživatel musí být přihlášen).


  • Každý FULL-C program může nastavit tzv. "brand" (uživatelskou značku / popisek) pro zařízení SDS, na kterém je spuštěn. Protože SDS je často dodáváno v rámci OEM pro další zástavbu do většího celku, může si finální systémový integrátor nastavit své doplňující texty (název firmy, odkazy na web, telefonní čísla na podporu, doplňující texty, informace o sériovém čísle atd.), které jsou pak zobrazeny v rámci stránek webové administrace zařízení SDS (login, welcome, atd.). Toto nastavení provádí FULL-C program zavolání SDS_set_a() s příslušným indexem a textem. Typicke se takový FULL-C kód dává na úplný začátek programu.


  • FULL-C program lze nachystat a vydat tak, že je "zablokován" tak aby fungoval pouze na jediném konkrétním vybraném SDS zařízení. Každé SDS má své unikátní sériové číslo (viz tabulka indexů pro SDSget_u() ), a FULL-C program může tuto hodnotu porovnat (s tím, co do FULL-C programu vloží autor programu). Toto uživatelské zablokování programu může předejít chybám, kdy by se např. nevhodný program nahrál omylem na jiný hardware. Pokud vydáváte FULL-C program prostřednictvím .FC souboru, kam zvolíte nepřiložit zdrojový .C kód, pak máte neprůstřelnou možnost zamezit použití programu na jakémkoliv jiném SDS než jaké sami určíte (zdrojový kód nebude cílovému uživateli k dispozici, a navíc váš program otestuje sériové číslo daného SDS na shodu). Samozřejmě to znamená, že musíte zmíněné sériové číslo daného SDS zjistit předem, než vydáte program, aby jste to číslo mohli do programu vložit.


  • Pokud ve svém programu využíváte sériovou sběrnici RS485, dbejte na správné zapojení sběrnice. Naše fórum je plné příkladů, kdy autor programu řešil problém s RS485 a nakonec se ukázalo, že sběrnice byla špatně zapojená (přehozené A/B dráty, nezakončená rezistory, neuvedená do známého klidového stavu přes rezistory atd.). Pro ověření programu je vždy dobré vzít pasivní "odposlech" sběrnice (osciloskop, osobní počítač) a zkontrolovat "překryv" (kdy více zařízení vysílá najednou, což je na RS485 kolize a poškození dat). Právě "překryv" je nejčastějším problémem způsobující poškození komunikace, hned po špatném nastavení komunikační rychlosti v programu. Vždy zkontrolujte svůj hardware, tj. celou sběrnici, je-li správně impedančně zakončena (nejvzdálenější konce) a zda-li jste správně zapojili oba klidové rezistory (pozor, na SDS-BIG tyto nejsou a musí se ručně doplnit "zvenku").


  • Pokud chcete přes sdílené proměnné (sv) přenášet čísla datového typu float (desetinná čísla), a pak je chcete například zobrazit na UserWEB webové stránce (a nebo přenést na server atd.), použijte následující trik. Vytiskněte obsah float čísla do Txx[] proměnné, pomocí sprintf(), nikoiv však s využitím %f - ale právě přes následující kód:
// definujeme si sdilenou promennou T00
char T00[32];

void FloatToT00(void)
{
 // vstupni float
 float xf;

 // pomocna promenna (presne 4 bajty, protoze "float" je ve FULL-C reprezentovan jako 32-bit)
 unsigned int xw;

 // zapiseme vzorovou hodnotu
 xf = 12.34;

 // ziskej 32bit obsah xf (= cteni 4 bajtu dat ktere reprezentuji dany float)
 xw = *(unsigned int *)&xf;
 // a zapis do T00, zarovnej vzdy na osm mist (at se to da pozdeji spravne zpracovat - prevest zpet na float)
 sprintf((char *)T00, "%08X", xw);

 // T00[] ted obsahuje "414570A4" (coz je hex reprezentace puvodni float hodnoty 12.34)
}

Pokud se po spuštění tohoto kódu pak podíváte např. na 192.168.1.250/shared.txt , tak tam uvidíte toto: "T00|414570A4|" (krom jiného textu). S tím už pak v JavaScriptu není problém pracovat (převést zpět na float a korektně zobrazit nebo dále zpracovat). Řada přímých příkladu je k dispozici, stačí hledat na Internetu podle klíčových slov "javascript convert hex to float". Toto se řeší právě z důvodu že FULL-C printf funkce (a další podobné) umí jen %f ale bez dalších parametrů, takže tisknou float čísla na všechny místa za desetinnou tečkou, což je ve většině případů nežádoucí (při zobrazování na webových stránkách a podobně).


  • Přistupovat ke všem údajům ze sběrnic 1-Wire, ve FULL-C, je samozřejmě možné, je však potřeba vědět jak. Detaily viz stránka FULL-C: OneWire.


  • Pamatujte, že SDS-64 (viz varianty) má k diposozi pouze celkem 64kB programové paměti (a SDS-128 má jen 128kB této paměti) - do které se musí vlézt celý váš přeložený FULL-C program, a k tomu také celý STACK a všechna potřebná paměť HEAP. Toto omezení není dáno programově, ale právě použitým hardware v SDS, takže jej nelze jednoduše "obejít" - pokud se vám to nevleze do varianty 64 tak musíte použít variantu 128, to je jediné řešení.


  • Vždy správně nastavte velikost STACKu - číselnou hodnotu nastavte v okně editoru zdrojového textu FULL-C. Pokud nastavíte hodnotu pro váš program jako moc malou, program nebude fungovat (při volání více funkcí může být pro nedostatek stacku ukončen). Příliš velká hodnota snižuje volnou paměť heap (pokud heap nepoužíváte, nastavte stack na vyšší hodnotu pro klid). Správnou hodnotu zjistíte určitým odhadem (podle velikosti a složitosti programu, podle toho kolik proměnných používáte a kolik parametrů se předává při volání funkcí, a kolik funkcí se v sobě volá). Nastavení je možné nechat automaticky odhadnou překladač, nebo jej můžete ručně zadat (odhad není přesný).


  • Inicializaci proměnných provádějte v kódu, nikoliv při deklaraci. Jde o to, že při inicializaci hodnot v proměnné či poli, provedené kódem, se spotřebuje zlomek stack paměti, oproti inicializace přímo při deklaraci. Stack paměti je vždy málo a je potřeba jí šetřit. Je to vlastnost FULL-C.

Příklad inicializace při deklaraci:

unsigned int numbers[5] = {0,1,2,3,4}; // toto spotřebuje hodně stacku - TAKHLE TEDY NE

Příklad identické inicializace kódem:

// toto řešení je doporučené, nespotřebovává stack
unsigned int numbers[5];
number[0] = 0;
number[1] = 1;
number[2] = 2;
number[3] = 3;
number[4] = 4;

Nebo, příklad odpovídající inicializace kódem:

// toto řešení je doporučené, nespotřebovává stack
  unsigned int numbers[5];
  unsigned int i;
  for (i = 0; i < 5; i++) { number[0] = i; };


>>>>>>>>>>>> Následující informace je platná pouze pro firmware starší než 10.7.2017

  • SDS ukazuje aktuální využití své 64kB programové paměti, na webové stránce v administraci.
HEAP STATS: curalloc=1892, totfree=58632 maxfree=58632, nget=12 nrel=0
HEAP STATS: bc_locked=1120, stackTop=2984 (of 5000)

Jak to dékódovat: první řádek ukazuje celkové využití paměti a počet alokací a uvolnění:

curalloc  = celkem alokovaná paměť (heap malloc, a všechny interní FULL-C funkce a interní data v SDS)
totfree   = celkem zbývající volná (alokovatelná) heap pamět (součet všech volných heap fragmentů)
maxfree   = největší volný heap fragment, který je možné alokovat v jednom kuse
nget      = celkový počet alokací (volání malloc/calloc) (včetně vnitřních volání interních FULL-C funkcí v SDS)
nrel      = celkový počet uvolnění předchozích alokací (volání free() funkce)

Druhý řádek se vztahuje na FULL-C program samotný a jeho běh:

bc_locked = kolik bajtů paměti je využito (uzamčeno) pro 'bytecode' vašeho FULL-C (kolik si vzal samotný kód FULL-C programu v paměti)
stackTop  = kolik bajtů zásobníku (stacku) je v tento okamžik využito (pokud je to na limitu, víte, že musíte upravit nastavení při překladu)
(of X)    = kolik bajtů může nejvíce zásobník (stack) zabrat z celé paměti (nastaveno uživatelem před překladem)


>>>>>>>>>>>> Následující informace je platná pouze pro firmware 10.7.2017 (včetně) a novější (viz také varianty)

  • SDS ukazuje aktuální využití své 64kB nebo 128kB programové paměti, na webové stránce v administraci.
FC: VER 0x08, PROGMEM 128kB
MEM STATS:
- HEAP: used=(492 of 120675), totfree=120164 (biggest=65396), num(get=11,rel=0)
- STACK: used=(364 of 10004)
- BC: locked=(264+128+388)
- SYS: free=280

Jak to dékodovat: první řádek ukazuje informace o SDS:

VER        = verze FC
PROGMEM    = varianta hardware (64kB nebo 128kB celkové paměti pro program a data)

Druhý řádek ukazuje celkové využití paměti a počet alokací a uvolnění:

HEAP used  = celkem alokovaná paměť (heap malloc, a všechny interní FULL-C funkce a interní data v SDS)
totfree    = celkem zbývající volná (alokovatelná) heap pamět (součet všech volných heap fragmentů)
biggest    = největší volný heap fragment, který je možné alokovat v jednom kuse
nget       = celkový počet alokací (volání malloc/calloc) (včetně vnitřních volání interních FULL-C funkcí v SDS)
nrel       = celkový počet uvolnění předchozích alokací (volání free() funkce)

Druhý řádek se vztahuje na FULL-C program samotný a jeho běh:

STACK used = kolik bajtů zásobníku (stacku) je v tento okamžik využito (pokud je to na limitu, víte, že musíte upravit nastavení při překladu)

Třetí řádek se vztahuje na FULL-C program samotný a jeho běh:

BC locked  = kolik bajtů paměti je využito (uzamčeno) pro 'bytecode' vašeho FULL-C (kolik si vzal samotný kód FULL-C programu v paměti)

Čtvrtý řádek nemá pro FULL-C význam:

SYS        = interní hodnota

Jak funguje FULL-C v SDS

Zde jsou popsány hlubší technologické detaily popisující princip FULL-C. Zajímat budou zejména ty, kteří pracují na složitých programech.

Program je uživatelem zadán jako jeden jediný textový soubor - zdrojový kód v jazyce FULL-C. Tento zdrojový kód (text) je zpracován v naší Windows aplikaci (FULLC.exe, k dispozici ke stažení na této WiKi) do formy výsledného souboru, který primárně obsahuje tzv. bytecode. Tento soubor je následně přes síť nahrán do konkrétního zařízení SDS, kde je trvale uložen (až do nahrání jiného souboru) a je zařízením SDS spouštěn.

Komprese

Program je přenášen z FULLC.exe do SDS přes počítačovou síť. Aby se nemuselo přenášet tolik dat, je bytecode komprimován. Typická úspora je 25% původního objemu.

S touto informací je potřeba plánovat při navrhování rozložení paměti - aby se tam vše vešlo. To že komprimovaný program (výstup FULLC.exe) se do paměti SDS na první pohled "vleze", ještě neznamená, že se program i podaří spustit (stále se může "vlézt", ale nezbude místo na zásobník nebo haldu - a to je konec).


ZÁKLADNÍ OMEZENÍ:

Zařízení SDS je k dispozici ve dvou základních variantách: varianta "64" má k dispozici 64kB paměti pro celkové potřeby programu, a varianta "128" má k dispozici 2x64kB (tj. 128kB) paměti pro celkové potřeby programu.

Do celé této paměti se musí vlézt nejen surový bytecode ale i všechny provozní záležitostí (zásobník - stack, proměnné, halda - heap).

Skladba paměti - varianta "64":

(START I-64kB BLOKU)

          0 + zásobník (stack)
              ...
            + řetězce programu (string blob)
              ...
            + instrukce programu (bytecode)
              ...
            + nevyužitelná rezerva systému (zhruba 260 bajtů)
              ...
            + proměnné programu (variables)
              ...
            + halda (heap)
              ...
       64kB + konec 

(KONEC I-64kB BLOKU)

Další paměť (více než 1x 64kB) už není ve variantě "64" k dispozici - vše se tedy musí vlézt do jednoho 64kB velkého bloku.


Skladba paměti - varianta "128":

(START I-64kB BLOKU)

          0 + zásobník (stack)
              ...
            + řetězce programu (string blob)
              ...
            + nevyužitelná rezerva systému (zhruba 260 bajtů)
              ...
            + proměnné programu (variables)
              ...
            + první část haldy (heap-1)
              ...
       64kB + konec 

(KONEC I-64kB BLOKU)

(START II-64kB BLOKU)

          0 + instrukce programu (bytecode)
              ...
            + druhá část haldy (heap-2), jen pokud nezabere program celý II-64kB blok
              ...
       64kB + konec

(KONEC II-64kB BLOKU)

Jak lze vidět, varianta "128" nabízí dva bloky po 64kB. Tyto bloky na sebe sice lineárně nenavazují (tj. součtem je to sice 128kB, ale ne v celku), nicméně přináší významnou úlevu předchozímu omezení původní varianty "64".

Pro použití varianty "128" je ovšem potřeba mít správný hardware (SDS), tzn. SDS výrobní varianty "64" nelze programově změnit na variantu "128" - to lze jedině použitím vhodného SDS hardware.


Další informace:

Velikost programu (bytecode + stringblob) a proměnných (variables) je pevná, a je přímo dána vámi tj. programátorem (podle toho, jaký napíšete program, tak bude tato oblast velká).

Zbytek je použit pro haldu (heap), což je různě veliký paměťový prostor který má program k dispozici (funkce: malloc, realloc, free), pokud jej tedy potřebuje. Pokud bude váš program moc velký, pak se může stát že halda nemusí existovat - a systém nebude schopen takový program vůbec spustit ! Vždy je potřeba mít haldo o velikosti alespoň hodnoty 1/8 velikosti programu.

Zásobník (stack) je vždy umístěn na začátku paměťového prostoru, a jeho velikost je opět jednorázově určena programátorem (podle odhadu nebo aktuální měření). Pokud je zásobník příliš malý, může být program předčasně ukončen (řešením je zvětšení velikosti zásobníku (na úkor velikosti heapu) a nové nahraní programu do zařízení). Toto je zřejmě nejčastější problém u většiny FULL-C programů - špatně nastavená velikost zásobníku.

Informace o aktuálním vyžití celého paměťového prostoru je k dispozici ve webovém rozhraní SDS (Menu, FULL-C). Odsud lze také přečíst skutečné využití zásobníku (nejvyšší dosažená velikost).


Heap

Typický FULL-C program v zařízení SDS nemá potřebu používat haldu (heap) - typicky všechny proměnné (i velká pole) lze bez problému staticky deklarovat.

Pokud však potřebujete dynamickou alokaci paměti, tak je k dispozici ten zbytek paměťového prostoru, který zůstane "volný" po nahrání programu (a vyhrazení zásobníku).

Informace o velikosti volného heapu a fragmentaci lze opět nalézt ve webovém rozhraní SDS.


Opakování varování

Velikost programu je omezena takto:

  • Vše se musí vlézt do 64kB (bytecode, proměnné, zásobník) popřípadě do 2x 64kB (128kB s danými omezeními).
  • Zásobník (stack) musí mít dostatečnou velikost pro provoz programu (všechny vnořené volání funkcí, rekurze, atd.).
  • Halda (heap) musí být k dispozici o dostatečné velikosti, jinak se jakýkoliv program nespustí.

Pokud by váš přeložený program byl moc velký, a to tak že se spolu s ním do paměti nevlezou další potřebné věci (zásobník atd.), nebudete moct program na zařízení SDS spustit.

Vždy můžete kontrolovat stav programu (a využití paměti) na stránce "FULL-C" ve webové administraci zařízení SDS.


Jak programovat

FULL-C program musí vždy mít definovanou funkci main(), která je první spuštěnou funkcí v rámci spuštění programu.

void main(void)
 {
   ;
 }

Je úkolem programátora, aby funkce main() nebyla opuštěna (potom je program "ukončen" protože nemá kam dál pokračovat) = v takovém případě je program ukončen, a znovu bude spuštěn až po restartu SDS (nebo když by byl nahrán nový program, tak je pak spuštěn ten).

FULL-C program tedy vždy začíná jako první provádět instrukce ve funkci main().


V základu lze činnosti provádět na základě těchto vstupů:

  • čas (např. každou vteřinu)
  • hardware (např. čtení vstupů)
  • hodnoty sdílených proměnných (např. změnou proměnné z uživatelské webové stránky)

Výstupy programu mohou být:

  • ovládání hardware (např. spínání relé)
  • změna hodnot sdílených proměnných (např. pro dynamické zobrazení na uživatelské webové stránce)
  • odesílání stavů přes síť (např. HTTP-GET nebo SNMP-TRAP)


Pro všechny tyto vypsané činnosti je potřeba znát funkce a příkazy, které programu umožní právě takovéto činnosti vykonávat.