Při psaní programu strávíte mnoho času (podle mě většinu) jeho laděním - hledáním chyb (bugů). Existuje mnoho technik jak bugy vyhledávat a my se v tomto tutoriálu podíváme na vybrané nástroje, které nám k tomuto účelu dává debugger (ST-link) v kombinaci s naším IDE (STVD). Stejné nebo obdobné prostředky najdete i na jiných platformách jako je třeba AVR nebo STM32.
Kliknutím na Debug -> Start Debugging se přepne vzhled okna do "debug" módu. Poté se program nahraje do mikropočítače a zůstane zastavený na první instrukci. Ta se nachází ještě před funkcí main (tedy před začátkem vašeho programu). Dříve než se zavolá funkce main, vykoná program v mikropočítači spoustu "přípravných" úkonů. Mezi jinými třeba inicializuje obsah proměnných. V pravé straně obrazovky vidíte okno "dissasembly" kde žlutě svítí první instrukce (na adrese 0x8000, což je začátek flash paměti). O tom že program neběží se můžete dozvědět i v pravém dolním rohu obrazovky ze zelené ikony s nápisem stop.
Dejme tomu že chcete aby program doběhl až na první příkaz ve funkci main. Tedy na místo odkud začíná váš program. Umístíte kurzor na první příkaz ve funkci main (kliknutím kamkoli na řádek) a poté použijete funkci Debug - Run to cursor. Program se rozběhne a zastaví se na zvoleném řádku - tedy na prvním příkazu vašeho programu. Žlutě podbarvený řádek symbolizuje místo kde váš program bude pokračovat, když k tomu dáte pokyn. Jinak řečeno příkaz na tomto řádku ještě není proveden. Pro další práci nebudete typicky potřebovat okno dissasembly, takže ho můžete zavřít.
Chod programu lze ovládat - krokovat. A to buď skrze menu Debug (které jsme viděli na předchozím obrázku), nebo skrze ikony v horní liště a nebo pomocí klávesových zkratek.
Velmi užitečná funkce při ladění programu jsou "breakpointy". Zjednodušeně řečeno můžete označit několik řádků na kterých se má program zastavit když na ně doběhne. Provést to můžete buď tak že pravým tlačítkem kliknete na vybraný řádek a dáte Instert/Remove Breakpoint nebo tak že kliknete vpravo vedle čísla řádku. Na tomto místě se pak objeví červený bod, který značí že je tam umístěn aktivní breakpoint. Program pak stačí rozběhnout (Continue) a na tomto řádku se sám zastaví.
Opětovným kliknutím na breakpoint červený bod "zešedne". Breakpoint přestane být aktivní. Alternativně to lze provést tak že klikneme pravým tlačítkem na řádek s breakpointem a zvolíme Enable/Disable Breakpoint. Dalším kliknutím pak breakpoint kompletně odstraníme.
Jistě vás napadne otázka, jaký je rozdíl mezi "neaktivním" a breakpointem a kompletně odstraněným ? Na žádném z nich se program nezastaví, takže k čemu je možnost "deaktivovat" breakpoint ? Smysl to má u větších programů. Představte si že máte rozsáhlý program a v něm máte rozmístěných mnoho breakpointů. Většinou nechcete mít všechny breakpointy aktivní, chcete mít možnost si podle potřeby některé zapnout a jiné vypnout. Pokud by jste je ručně odstraňovali a pak zase umísťovali, budete muset zdlouhavě listovat programem a hledat vždy příslušný řádek. A to je pracné. Aby jste toho byli ušetření, můžete si otevířt "seznam breakpointů" přes View - Instruction breakpoints a v něm si přehledně breakpointy aktivovat a deaktivovat dvojklikem na červené/šedé kolečko. Případně dvojklikem přesunout na řádek s breakpointem kurzor. Pravým tlačítkem pak můžete breakpointy i mazat nebo ovládat hromadně. Breakpointy jsou jedním z nejmocnějších prostředků pro debug které máte v našem případě k dispozici.
V okně si u každého breakpointu můžete všimnou i sloupečku counter a condition. Ty slouží k tomu aby bylo možné breakpoint ještě nějak podmiňovat. Pokud například do condition napíšu x>15 tak to znamená že se má program na breakpointu zastavit jedině tehdy pokud proměnná x nabývá hodnoty větší jak 15. Položka counter pak říká kolikrát má IDE breakpoint ignorovat než program zastaví. Hodnota 2 znamená že se má program zastavit až při druhém průchodu breakpointem. Dejte ale pozor na to že ve skutečnosti se program zastaví vždy (!) když přijde na breakpoint, bez ohledu na podmínky. Teprve IDE podmínku vyhodnotí a případně pak zase program nechá běžet dál. Každý průchod breakpointem tedy program na dvě až tři desetiny sekundy zastaví. Kdybych napsal podmínku x>250, programu by trvalo minutu než by této hodnoty dosáhl. Protože by se program 250x zastavil a zase spustil. Kdežto v případě že by program běžel bez zastavování na breakpointu, trvalo by mu dosáhnout této hodnoty jen pár desítek mikrosekund ! To může značně ovlivnit běžný chod programu a je tedy potřeba být s pužitím takového podmiňování breakpointu obezřetný. Obdobný úkol (podmíněný brakpoint) lze realizovat daleko méně invazinvě - nástrojem advanced breakpoint, ale tomu budu věnovat samostatnou kapitolu.
Kliknutím na View -> Watch otevřete okno Watch. To slouží k prohlížení a editaci obsahu proměnných, případně k vyčíslení různých výrazů. Dvojklikem na řádek ve sloupci "variable" můžete psát nebo upravovat název proměnné kterou chcete sledovat. Ve sloupci "value" vidíte její hodnotu, ve sloupci "Type" je typ proměnné a ve sloupci "Address" je její adresa v paměti (typicky RAM). Hodnotu proměnné můžete zobrazovat v několika režimech.
Speciální režim "On Fly" aktivujete pravým tlačítkem -> Read/Write On Fly. Když pak program spustíte (Continue), bude IDE automaticky přibližně každou sekundu vyčítat obsah proměnných a zobrazovat ho a vy můžete sledovat co se s hodnotami děje při běžném chodu programu. Režim pak vypnete stejným postupem. Netuším jak moc je to invazivní a jestli IDE nějak zastavuje běžný chod programu. Podobnou funkci jako Watch má i okno Local Variables. Do něj, ale nezapisujeme názvy proměnných ani výrazy. V okně se zobrazuje hodnota lokálních proměnných (například proměných definovaných uvnitř funkce). Tato funkce potom úzce spolupracuje s funkcí "call stack".
Jak jistě víte, chod všech periferií může váš program sledovat a ovládat čtením a zápisem do registrů periferií. V našem případě za vás manipulaci s registry provádí hotové knihovní funkce. V budoucí praxi ale často nebudete s takovým přístupem spokojení (kvůli funkcionalitě či kvalitě knihoven, nebo efektivitě programu) a budete periferie ovládat přímo skrze registry. Pro snadnější orientaci máte tedy k dispozici okno View -> Peripheral Registers kde můžete jejich obsah sledovat a měnit. Okno je takové "spartánské" a chybí mu pokročilé funkce pro komfortní práci s jednotlivými bity registrů. Takže v praxi bude vyžadovat ruční počítání bitů, což jak jistě chápete je zdlouhavé a rizikové (člověk se snadno o jeden bit někde posune či přepočítá). Upozornění ! Pokud okno nepoužíváte, zavřete ho. Debugger musí registry číst aby vám mohl jejich obsah zobrazovat a čtení některých registrů může měnit chování periferie ! (Například čtení datového registru UARTu maže jeho vlajky atp.)
Tato funkce (View -> Call Stack) umožňuje monitorovat kudy a do jakých funkcí se váš program vnořoval a volit kontext lokálních proměnných pro okno "Local variables". Zastavíte-li program uvnitř funkce (třeba breakpointem), můžete dvojklikem na příslušný řádek v okně "Call stack" zvýraznit odkud ve vašem zdrojovém kódu byla funkce volána (a odkud bude program po skončení funkce pokračovat).
Nejpokročilejší debugovací nástroj, jaký nám STM8 nabízí je "advanced breakpoint" (někdy též data breakpoint, nebo watchpoint), který umí pracovat v mnoha různých módech. Veškeré vyhodnocování probíhá na hardwarové úrovni přímo v MCU, takže je neinvazivní a nijak nenarušuje běžný chod programu (na rozdíl od podmínek u běžných breakpointů). Samozřejmě až do chvíle kdy breakpoint program zastaví. Konfigurovat ho můžeme skrze okno Debug Instrument -> Advanced Break.... V seznamu si vybíráme módy. První položkou, označenou jako Disable Advanced break jej vypínáme. Vybral jsem několik pro vás nejzajímavějších režimů a ty si stručně okomentujeme. Schválně jsem nechal vpravo otevřené okno watch abychom měli přehled o adresách našich proměnných.
V tomto režimu si zvolíme adresu jednoho bytu v RAM, který chceme hlídat. V mém případě je to adresa 0x0 na které se nachází proměnná x. Dále zvolíme hodnotu kterou má proměnná mít, já volím 100. A ještě zvolíme zda má breakpoint hlídat pouze zápis do této proměnné, pouze čtení z ní, nebo jakýkoli přístup. Já zvolil zápis (Write). Takto nastavený breakpoint zastaví program v okamžiku kdy zapíše do proměnné x hodnotu 100. Dokud se to nestane, nijak to neovlivňuje chod programu. Na obrázku výše si můžete všimnou, že se program zastavil hned po příkazu x++, tedy ihned po provedení instrukce která do x zapsala hodnotu 100.
V tomto režimu breakpoint hlídá zda program přistupuje (zapisuje, čte, nebo cokoli z toho) do paměti v udaném rozmezí. Dejme tomu že chci program zastavit v okamžiku kdy zapíše kamkoli do pole arr. Zvolím tento režim, v okně watch se podívám že pole začíná na adrese 0x07 a končí na adrese 0x0D. Žlutý řádek naznačuje že se program zastavil ihned po vykonání příkazu arr[3]++ (zápisu do pole). Obdobně bych mohl volbou "read" hlídat kdy program z pole čte.
V tomto režimu breakpoint hlídá přístup (čtení, zápis nebo obě) na dvou různých adresách. Je to takový "dvojitý" breakpoint. Na rozdíl od prvního módu, ale nehlídá hodnotu na této adrese.
Při prvním spuštění debugu se vás studio zeptá jaký debugger má použít. V případě že omylem zvolíte špatnou variantu, nebo chcete použít simulátor, můžete se k této volbě kdykoli vrátit skrze horní menu položku Debug instrument -> Target Settings. Po otevření okna volíte Swim ST-link. Všímavého čtenáře možná zaujme check box "hot plug". Jak název naznačuje, je možné se k mikropočítači připojit "za běhu" aniž by ho debugger restartoval nebo do něj zapsal program. V praxi je to velmi užitečný nástroj pro odchytávání vzácných chyb, ale v našem případě prakticky nepoužitelný, protože to lze provést pouze v disasembleru. Tedy k mikropoítači se připojíme, můžeme sledovat obsah jeho paměti i surový program, ale ne v jazyce C. Nebudu tedy čtenáře ani obtěžovat postupem jak se "hot plug" provádí. Když to někdo nedej bože bude opravdu potřebovat (dovedu si takové situace představit), tak najde postup v manuálu k STVD (kap. 7.4).
Home
| V1.00 7.8.2023 /
| By Michal Dudka (m.dudka@seznam.cz) /