2.3 Programovací jazyk MicroPython
Mikrokontroléry môžeme programovať rôznymi spôsobmi, spomínali sme si už blokové (vizuálne) i textové programovanie. Z klasických textových programovacích jazykov sú k dispozícii:
Predstavenie jazyka Python
Python je jazyk interpretovaný, rovnako jeho MCU verzie, čo má svoje výhody i nevýhody, ktorými sme sa už vo všeobecnosti zaoberali. Najväčšími strašiakmi Pythonu v MCU sú:
Python je pomalší ako C.
Python spotrebuje veľa operačnej pamäte.
Zariadenie s Python nevydrží dlho bežať na batériu.
Tieto tvrdenia v súčasnosti nie sú celkom pravdivé. Výkon je možné zvýšiť až na úroveň skompilovaného kódu - výkonovo kritické časti je možné viacerými spôsobmi optimalizovať, či dokonca „predkompilovať“, čo pre bežné funkcie využíva aj samotný MicroPython. K zaujímavým výsledkom merania výkonu prišiel Vláďa Smitka z Makerclass, ako vidno v jeho prezentácii „Python na MCU - Je to dobrý nápad?“. V dokumentácii MicroPython je možné nájsť pokročilé tipy pre zvýšenie rýchlosti programu - nie je to však čítanie pre začiatočníkov! 🙂 Takejto vysokej úrovni optimalizácie sa v tomto kurze nebudeme venovať.
Vyššia spotreba pamäte je fakt - prekladač musí byť aktívny v pamäti, no samotný program už nemá výrazne vyššie požiadavky na pamäť. Keďže sú však už bežne dostupné lacné MCU rádovo s MiB operačnej pamäte, tento problém sa postupne stáva nepodstatným.
Čo sa týka výdrže batérie, teda spotreby energie, najpodstatnejšie je písať programy rozumne, s dôrazom na šetrenie energie a s využívaním „spánku“. Až na druhom mieste je spotreba energie pre samotný beh programu - je možné použiť podobné optimalizácie, ako pri výkone.
Každopádne, výkon behu programu nie je všetko a niekedy je oveľa podstatnejšia rýchlosť vývoja aplikácie. A koľko trvá kompilácia C++ pre MCU, sme mali možnosť vidieť a zažiť s ESPHome. 😒
MicroPython vs. Python
MicroPython je jednoduchou a efektívnou implementáciou programovacieho jazyka Python 3.4, pričom obsahuje aj niektoré novšie prvky z vyšších verzií - v dokumentácii MicroPython je možné nájsť rozdiely oproti štandardnému Python. Zahŕňa len časť štandardných knižníc Python, má však knižnice pre prácu s hardvérom a je optimalizovaný pre mikrokontroléry s obmedzenými zdrojmi. Snaží sa byť kompatibilný s plnohodnotným jazykom Python.
Základné konštrukcie jazyka MicroPython sú totožné s Python a môžeme teda využívať dokumentáciu jazyka Python. Oplatí sa však oboznámiť so špecifikami MicroPython - či už po stránke optimalizácie zápisov alebo špeciálnych knižníc určených pre ovládanie hardvéru.
Základné konštrukcie a syntax MicroPython
Pre komentár v jazyku Python slúži znak „#“ a pre viacriadkový komentár je možné využiť viacriadkový text, ktorý začína i končí trojicou apostrofov ('''):
# toto je bežný jednoriadkový komentár
'''
A toto je viacriadkový komentár.
Ono to vlastne ani nie je komentár, ale len tak „voľne pohodený“ viacriadkový textový reťazec.
Na rýchle znefunkčnenie časti programu však výborne poslúži.
'''
Operátory
Matematické operátory sú podobné C++:
Operátory porovnávania sú rovnaké ako v C++:
Logické operátory sa píšu slovne:
Ďalšie informácie: W3Schools - Python Operators
Premenné a dátové typy
V názvoch premenných sa rozlišujú malé a veľké písmená, môžeme používať aj diakritiku. Premenné nie je potrebné deklarovať, priamo môžeme definovať hodnotu: {názov premennej} = {hodnota}
počet = 10
pi = 3.14
Logické hodnoty (boolean) sú označované True a False:
je_horúco = False
Textové reťazce môžu byť v úvodzovkách i apostrofoch - môžeme si vybrať:
meno = "Dušan"
priezvisko = 'Zervan'
Typ premennej je možné zistiť funkciou type({premenná}) - tá vracia objekt, no pre získanie názvu typu vo forme textového reťazca môžeme využiť jeho vlastnosť .__name__:
type(meno).__name__
Je možné priraďovať naraz do viacerých premenných:
meno, vek = "Anna", 15
Vstup a výstup
Výpis z MicroPython na štandardný výstup sa nerealizuje „na obrazovku“, ale na konzolu / sériový port.
Príkaz print({výraz 1}, {výraz 2}, …) vypíše zadané výrazy oddelené medzerou, na konci bude ENTER:
print(meno, "získal z testu", počet, "bodov.")
Ukončenie riadku určuje parameter end (predvolený je ENTER - "\n"), oddeľovanie jednotlivých častí výpisu určuje parameter sep (predvolená je medzera):
print("údaje v CSV:", end=" ")
print(počet, poradie, meno, sep=";")
Premennú môžeme načítať z konzoly: {premenná} = input({text výzvy})
vek = input("Zadaj vek: ")
Používanie funkcií z modulov
Niektoré funkcie sú priamo v jazyku Python, no viac je v rôznych „moduloch“. Ide o podobnú situáciu ako predstavujú knižnice v C++. Pred použitím funkcií z modulu ich musíme importovať, čo je možné rôzne:
Prvý spôsob, teda import modulu, vyžaduje zadávať pred názov funkcie aj názov modulu:
import time, math
time.sleep(1)
print(time.time_ns(), math.pi)
Import funkcií z modulu umožňuje písať priamo názov funkcie, len je potrebné dať si pozor, aby sa funkcie z rôznych používaných modulov nevolali rovnako:
from time import *
from math import pi
sleep(1)
print(time_ns(), pi)
V oboch variantoch sa takto neimportujú len funkcie, ale aj konštanty a premenné.
Užitočné funkcie
Funkcie pre prácu s časom z modulu time umožňujú nielen získať systémový čas, ale aj pozastaviť vykonávanie programu a šetriť energiu, či merať trvanie úseku programu:
V module machine nájdeme v MicroPython funkcie týkajúce sa hardvéru, pre nás bude potrebných len zopár:
Funkcie z modulu random umožňujú pohodlnú prácu s náhodnými číslami:
Funkcie textového reťazca
Textový reťazec je objekt, jedná sa teda o objektové funkcie. Tieto funkcie vracajú nový objekt, nemenia pôvodný:
Textové reťazce je možné spojiť operátorom + a opakovať operátorom *:
print(3 * "Učiť sa! " + "- povedal súdruh Lenin")
Pre formátovaný výstup je možné využívať takzvaný f-string, ktorý je približne 2× rýchlejší ako funkcia format() a 3×rýchlejší ako spájanie textov cez plus:
print(f"číslo ∏: {pi:.4f} - zaokrúhlené na 4 desatinné miesta")
Ďalšie funkcie pre prácu s textovým reťazcom: W3Schools - Python String Methods
Kolekcie údajov
Python rozlišuje 3 typy „polí“:
Najbohatšiu ponuku funkcií majú zoznamy, niektoré funkcie sú dostupné aj pre tuple a množiny.
Zoznam (list)
Zoznam v Python sa správa podobne ako pole v C++, prvky sa číslujú od 0. Indexovať prvky je však možné aj od konca (záporným číslom) a prvky nemusia byť rovnakého dátového typu:
čísla = ["dvanásť", 15, "štyri", 3.14]
print(čísla[-1])
Počet prvkov zistí funkcia len({pole}) a súčet hodnôt v poli čísel spočíta funkcia sum({pole}).
Prvky zoznamu je možné uložiť do premenných - musí ich však byť rovnaký počet: {premenná 1}, {premenná 2}, … = {zoznam}
a, b, c, d = čísla
Zistenie výskytu prvku v poli - vráti bool: {hľadaný prvok} in {pole}
print(15 in čísla)
Delenie poľa od uvedeného začiatočného prvku (vrátane) po konečný prvok (bez neho): {pole}[{začiatok}:{koniec}]
print("druhé a tretie číslo:", čísla[1 : 3])
Rovnakým spôsobom môžeme aj prepisovať prvky na uvedenej pozícii:
Rovnako ako textové reťazce, aj zoznamy je možné spojiť operátorom + a opakovať operátorom *. A rovnako aj zoznam predstavuje objekt, vloženie prvku do zoznamu je možné vykonať objektovými funkciami:
Odobratie prvku zo zoznamu:
Ďalšie zaujímavé funkcie zoznamu:
Viac funkcií pre prácu so zoznamom: W3Schools - Python List Methods
Tupla (tuple): zátvorky ()
Tupla je podobná kolekcia údajov ako zoznam, ale prvky sa nemôžu meniť, ide teda o pevne daný - nemenný zoznam, rýchlejšie spracovávaný. Môže sa jednať aj o jediný prvok, no vtedy musí končiť čiarkou: {tupla} = ({prvok},)
Tupla poskytuje tie isté operácie a funkcie, ako majú zoznamy - s výnimkou tých, ktoré menia hodnoty.
Viac funkcií pre tuple: W3Schools - Python Tuple Methods
Množina (set): zátvorky {}
Na rozdiel od zoznamu a tuply, prvky množiny nemajú svoje poradie, teda nie je ich možné indexovať a nemôžu sa opakovať (žiadne duplikáty).
Do množiny môžeme prvok pridávať:
A tiež odoberať:
Viac funkcií pre množinu: W3Schools - Python Set Methods
Slovník (dictionary)
Slovník v Python organizuje údaje v pároch kľúč:hodnota rovnako ako JSON: {slovník} = { {kľúč}: {hodnota}, … }
Využiť ho možno podobne ako štruktúry v C++:
osoba = {
"meno": "Fero",
"priezvisko": "Kováč",
"vek": 1212,
"bicykel": "horský"
}
print(osoba["meno"])
Ide o podobná formu ako množina - neumožňuje duplikáty, čiže dva údaje nemôžu mať rovnaký kľúč. Prístup ku jednotlivým prvkom je podobný ako pri zozname, no namiesto indexu uvádzame kľúč. Pokiaľ kľúč neexistuje, program skončí s chybou, preto je vhodnejšie využiť funkciu get():
Pre prechádzanie slovníkom (nižšie uvedeným cyklom for) sú užitočné tieto funkcie:
Pojem „pohľad“ vyjadruje skutočnosť, že prepojenie je „živé“, teda pri zmene údajov v pôvodnom slovníku sa zmenia aj údaje cez pohľad - a to aj v prípade, že sme ho priradili do premennej. Funkcie je niekedy možné využiť aj bez cyklu, napríklad:
print("O osobe evidujeme tieto údaje:", ", ".join(osoba.keys()))
# O osobe evidujeme tieto údaje: meno, priezvisko, vek, bicykel
Prehľad ďalších funkcií pre slovník: W3Schools - Python Dictionary Methods
Podmienený príkaz: if - elif - else
Je veľmi podobný C++, má mierne odlišnú syntax a je ho nutné správne predsadiť!
Časť elif sa môže opakovať viackrát, no nie je povinná ani časť elif, ani časť else. Môžeme samozrejme využívať aj vnorené podmienky, opäť treba správne predsadiť.
Cyklus podľa podmienky: while
fungujeFunguje rovnako ako v C++, no je mierne odlišná syntax:
⁘
- while {podmienka}:
⁘ - {príkazy tela cyklu}
tiežTiež je možné použiť break (predčasné ukončenie cyklu) a continue (predčasný prechod na ďalšiu iteráciu) - rovnako ako v C++
cyklusCyklus môže obsahovať aj nepovinnú časť else:else - vykoná sa pri riadnom ukončení po poslednejnesplnení iteráciipodmienky (teda nie po ukončení cez break)break):
from time import ticks_ms, ticks_diff
print("veľmi hlúpym spôsobom počkám 2 sekundy...", end = "")
t1 = ticks_ms()
while ticks_diff(ticks_ms(), t1) < 2000:
pass # prázdne telo = nerobí „nič“, ale ani nešetrí batériu - toto sa nerobí!
else:
print(" OK")
Cyklus pre zoznam a interval hodnôt: for
Cyklus for sa odlišuje sa od C++, používa sa na prechod sekvenciou (zoznam, tupla, textový reťazec):
⁘
- for {premenná} in {zoznam}:
⁘ - {príkazy tela cyklu}
⁘Cyklus forteda x in [1, 2, 4, 8]:
⁘ print(x)
vytvorí lokálnu premennú,premennú a bude v nej iterovať hodnoty zoz zoznamu
výrazysekvencie. break,Výrazy break, continue a else je možné používať rovnako ako v cykle while:
for x in ("Písmenká"):
print(x, end = ", ")
else:
print("\b\b.")
# napíše: P, í, s, m, e, n, k, á.
preMôžeme tiež prechádzať slovníkom, využijúc jeho funkcie keys(), values(), či items() - pozri vyššie pri opise slovníka:
osoba = {"meno": "Fero", "priezvisko": "Kováč", "vek": 12, "bicykel": "horský"}
print("O osobe evidujeme tieto údaje:")
for názov, hodnota in osoba.items():
print(f"- {názov}: {hodnota}")
Pre vytvorenie zoznamu čísel môžeme využiť funkciu range():
⁘
- range({počet}) - od čísla 0 po {
počet} -počet- 1⁘}; - range({začiatok}, {koniec}) - od začiatku (vrátane) po koniec (bez);
⁘
príklad:
⁘
for xi in range(10, 30, 5):
⁘
print(x)i, end = " ")
# napíše: 10 15 20 25
Tip: Pokiaľ v cykle for nie je premenná iterácie potrebná, zvykne sa nazvať „_“.
Definícia funkcie
priPri definícii funkcie neuvádzame ani návratovú hodnotu, ani typ parametrov, inak je podobná C++
⁘ def {názov} ({parametre}):
Podobne ako v C++, funkcia nemusí mať parametre, no zátvorky musia byť uvedené
parametreParametre môžu byť nepovinnénepovinné, (teda mať predvolenú hodnotu)
výsledokVýsledok (jedinú návratovú hodnotu) je možné odovzdať a funkciu ukončiť cez return. Volanie (použitie) funkcie je podobné C++.
Funkcia môže používať globálne premenné, v tele funkcie ich je treba deklarovať výrazom global:
def ZvysPocet(pridaj = 1):
global pocet
pocet += pridaj
pocet = 100
ZvysPocet(5)
print(pocet) # 105
Špecialitou je možnosť vrátiť viac výsledkov je možné odovzdať cez yield - ide o generátor, funkcia sa neukončíneukončí:
funkcia
def môžeMocniny(zaklad, používaťpocet):
globálnem premenné,= deklaruje1
ichfor výraz_ globalin range(pocet):
m = m * zaklad
yield m
for m in Mocniny(2, 10):
print(m, end = " ")
volanieGenerátory (použitie)však funkcieposkytujú jepodstatne podobnéviac C++možností: W3Schools - Python Generators
Parametre funkcie
parametrePri volaní funkcie je možné volaťvynechať sktorýkoľvek menomnepovinný parameter a menovite nastaviť potrebné parametre v ľubovoľnom poradí, napr.:poradí:
⁘
def Priemer(AbsPriemer(sucet, pocet)pocet = 1):
if ⁘pocet: return abs(sucet / pocet)
return None
print(AbsPriemer(pocet = 7, sucet = -22))
Funkcia si môže vynútiť použitie pomenovaných parametrov alebo pozičných parametrov, či rôzne kombinácie: W3Schools - Python Function Arguments
⁘ print(Priemer(pocet=10, sucet=45)) # 4.5
funkciaFunkcia môže mať aj viacnásobný parameter:
- ak názovje začínapred znakomnázvom parametra znak *, používa sa ako tupletupla:
-
def ak názov začína znakmiPozdrav(pozdrav, *mena):
for meno in mena:
print(f"{pozdrav} {meno}!")
Pozdrav("Nazdar", "Fero", "Adam", "Cyril")
# Nazdar Fero!
# Nazdar Adam!
# Nazdar Cyril!
Pokročilý tip: Ak sú pred názvom parametra dva znaky **, používa sa ako slovník s názvom parametra pri volanívolaní:
def AbsPriemer(**parametre):
sucet = pocet = 0
for p in parametre:
if p == "sucet":
sucet = parametre[p]
elif p == "pocet":
pocet = parametre[p]
else:
print(f"neznámy parameter: {p}")
if pocet: return abs(sucet / pocet)
return None
print(AbsPriemer(pocet = 7, sucet = -22, pokus = 3))
Viac príkladov na viacnásobné parametre: W3Schools - Python *args and **kwargs
Lambda
lambdaLambda je jednoduchá anonymná funkcia, ktorú formálne môžeme priradiť do premennej:funkcia:
⁘
- lambda {parametre} : {výraz s parametrami}
Lambdu môžeme formálne priradiť aj do premennej, vtedy sa z nej v podstate stane pomenovaná funkcia:
príklad:
⁘ SúčetČísel
SucetCisel = lambda x : (1 + x) * x / 2
print(SucetCisel(10)) # 55.0
Užitočnejšia je však v situácii, keď potrebujeme poslať funkciu ako parameter inej funkcie:
⁘
def UpravZoznam(zoznam, funkcia):
for i in range(len(zoznam)):
zoznam[i] = funkcia(zoznam[i])
cisla = [1, 2, 3, 5, 7, 11, 13]
UpravZoznam(cisla, lambda x : x**2)
print(SúčetČísel(10))cisla)
# [1, 4, 9, 25, 49, 121, 169]
Ešte zaujímavejšie (a pokročilejšie) využitie: W3Schools - Python Lambda
Mapovanie cez funkciu
viac:V https:súvislosti s funkciami je veľmi užitočné poznať mapovaciu funkciu map(), ktorá umožňuje postupne volať zadanú funkciu pre každý z parametrov:
Užitočné to môže byť aj na konverziu typu - napríklad textová funkcia join() vyžaduje textové parametre a nefungovala by s číslami:
cisla = (1, 2, 3, 5, 7, 11, 13)
print("zoznam čísel ako texty:", list(map(str, cisla)))
print("zoznam čísel spojený:", ", ".join(map(str, cisla)))
# zoznam čísel ako text: ['1', '2', '3', '5', '7', '11', '13']
# zoznam čísel spojený: 1, 2, 3, 5, 7, 11, 13
Viac príkladov a možností: W3Schools - Python map() Function
Zachytávanie výnimiek (chýb)
princípPrincíp je rovnaký ako v C++, mierne odlišná syntax:
⁘
- try:
⁘ {príkazy}
⁘ except:
⁘ {príkazy, v ktorých hrozí chyba}
⁘
⁘
častiČasti else a finally nie sú povinné
príkazy finally sa vykonajú vždy, a to aj v prípade predčasného ukončenia cez break a return
zaZa výrazom except môže byť uvedený typ zachytávanej chyby (všeobecne akákoľvek chyba sa označuje Exception) a môže sa opakovať s rôznymi typmi chýb. Prípadne je možné zachytiť typ chyby a uložiť ho do premennej pre výpis:
try:
print(10/0)
except Exception as e:
print("Nastala chyba:", e)
viac:Podrobnejšie https://www.w3schools.com/python/python_try_except.aspinformácie: W3Schools - Python Try Except
Inštalácia rozširujúcich modulov
rozširujúceRozširujúce moduly (knižnice) môžeme nahrať ručne do priečinku /lib alebo do aktuálneho / koreňového priečinku
akAk je modul prítomný na viacerých miestach, uplatní sa priorita definovaná v sys.path.
predvolenáPredvolená priorita v MicroPython je:
- aktuálny
priečinok,priečinok;
vV školskej inštalácii je priorita zmenená nasledovne:
- aktuálny
priečinok,priečinok;
pokiaľPokiaľ jemá mikrokontrolér aktívne pripojenie do internetu (nanapríklad cez Wi-Fi), je možné automatizovane stiahnuť modul do /lib,lib, stačí poznať jeho názov:
⁘
import mip
⁘
mip.install("{názov modulu}"modulu")
Trieda (class)
...