IR přijímač KY-022 s ESP32 a MicroPythonem
Proč právě IR, tedy infračervený bezdrátový přenos? Infračervené ovládání patří mezi nejrozšířenější technologie dálkového řízení – je levné, energeticky úsporné a dostatečně spolehlivé. Najdeme ho v dálkových ovladačích k televizím, audiotechnice, klimatizacím, hračkám i celé řadě dalších zařízení.
Jeho hlavní výhodou pro nás je snadná dostupnost, spolehlivost a – jak si ukážeme – i poměrně jednoduchá integrace do MicroPython projektu. Naprogramovat vlastní IR přijímač není nijak složité. A ruku na srdce: kdo by nechtěl ovládat svůj projekt pohodlně z gauče, stejně jako televizi?
Princip IR ovládání
Než se pustíme do dalších „bastlířských“ hrátek, tentokráte s IR přijímačem, podívejme se, jak vlastně infračervené dálkové ovládání funguje.
Infračervené elektromagnetické záření je blízké viditelnému světlu, jen má delší vlnovou délku – a proto ho lidské oko nevidí. V technice se začalo využívat například právě v dálkových ovladačích k televizím, k jeho rozšíření výrazně přispěla i nízká cena IR komponentů. Protože se IR záření chová podobně jako světlo, nemusíme řešit odstínění například jako u radiových vysílačů – takže se pak nestane, že budete omylem přepínat televizi sousedovi přes zeď, pokud má stejný ovladač. 😊
Výhodou tohoto „neviditelného světla“ je, že ho běžné světlo příliš neruší. Na druhou stranu, i běžné tepelné zdroje – žárovky, ale i Slunce – kolem sebe šíří dost IR záření. Ovládání tedy rozhodně nemůže fungovat podle principu: IR záření dopadá na čidlo → HIGH, nedopadá → LOW. Jasné osvícení a zatemnění sice roli hraje, ale ne tak jednoduše.
Celý trik je v tom, že signál je modulovaný – a právě tuto modulaci náš IR přijímač rozpoznává. Dálkový ovladač s IR LED vlastně „vyblikává“ konkrétní kód, který nese určitou informaci. A tu je třeba dekódovat. Díky přesně dané frekvenci (obvykle 38 kHz) se navíc minimalizuje rušení okolním IR šumem – v přírodě totiž jen těžko najdete zdroj, který by přesně pulzoval na 38 kHz.
Obvod IR přijímače
Obvod IR přijímače (viz obrázek č. 1) se skládá z infračerveného senzoru, který převádí světlo vysílané IR diodou na odpovídající elektrický signál. Ten je pak třeba obvykle zesílit a demodulovat – výsledkem je digitální signál, který na výstupu přechází mezi hodnotami LOW a HIGH. Tento signál pak využijeme pro obnovení původní, vyslaná informace.
V našem experimentu použijeme hotový IR set, který je běžně a levně k sehnání pro modul Arduino. Obsahuje infračervený dálkový ovladač (ale můžete klidně použít i ten od televize nebo Hi-Fi věže), modul IR přijímače KY-022, IR LED diodu (tu si zatím schováme na jiné pokusy) a několik dupont kabelů na propojení (viz obrázek č. 1).

IR přijímač HX1838 – modul KY-022
Modul KY-022 využívá senzor HX1838, který má už v sobě integrovaný obvod pro příjem, zesílení i demodulaci IR signálu. Na jeho výstupu tak rovnou získáme digitální signál s úrovněmi HIGH/LOW.

Modul reaguje na infračervené záření s vlnovou délkou přibližně 840–850 nm. S běžnou IR LED diodou v této oblasti docílíme dosahu až 10 metrů – v některých případech i více (uvádí se až 18 m). Napájecí napětí se pohybuje mezi 2,7 V a 5,5 V, což je ideální pro použití s modulem ESP32, který pracuje s logikou a napájením 3,3 V.

Destička modulu KY-022 má tři vývody: dva slouží k napájení (+
a –
; provozní proud 0,4–1,5 mA), třetí pin (S
) poskytuje digitální výstup. Schéma elektronického zapojení modulu najdete na následujícím obrázku č. 4.

Jak vidíme na schématu zapojení modulu (obr. 4), senzor VS1838B (HX1838) je doplněn o LED diodu, která signalizuje logickou nulu na výstupu. Při příjmu signálu tedy poblikává – tím dává najevo, že senzor zachytil IR záření.
Blokové schéma IR čidla VS1838B (viz obrázek č. 1) ukazuje, že uvnitř senzoru se na výstupu nachází pull-up rezistor vůči napájení. Ten by znamenal určité riziko pro modul ESP32, pokud bychom modul senzoru chtěli napájet napětím 5 V. Při připojování k modulu ESP32 je tedy důležité dodržet napájení 3,3 V.
A když už jsme u připojení k modulu ESP32, pojďme si to rovnou ukázat. Pro napájení použijeme piny GND
a 3V3
. Signál z IR čidla pak připojíme na libovolný GPIO pin, který lze nastavit jako digitální vstup. My jsme zvolili GPIO15
, ale použít lze téměř jakýkoliv jiný. Následující obrázek (obr. č. 5) ukazuje konkrétní propojení IR modulu KY-022 s mikrokontrolérem ESP32, detaily pak shrnuje připojená tabulka.

IR modul (KY-022) | Modul ESP32 |
---|---|
– | GND |
+ | 3V3 |
S | GPIO15 |
Jako obvykle si nejprve vyzkoušíme přímé čtení IR signálu – ať vidíme, jak senzor funguje. Poté si ukážeme, jak práci zjednodušit pomocí knihovny. A protože se už skoro stává nepříjemným pravidlem, že knihovny na ESP32 nejsou vždy úplně bez problémů, opět se nám budou hodit zkušenosti z předchozích článků.
Přímé načítání IR čidla:
Prvním způsobem, jak získat data z IR čidla, je přímé načítání výstupního pinu a ruční vyhodnocování jednotlivých přijatých signálů. Ve své podstatě tak budeme „rekonstruovat“ bitový řetězec, který byl čidlem zachycen.
Jak už jsme naznačili, odeslaný kód (sériový tok bitů) je modulován pomocí protokolu NEC na základní přenosovou frekvenci – obvykle 38 kHz. Na následujícím obrázku vidíme, jak je reprezentována úroveň logické „0“ a „1“ – vždy je „vyblikán“ signál délky 560 μs, následná délka mezery určuje odeslanou úroveň.

Jak vypadá signál (NEC protokol)
Modulovaný signál je přesně to, co „vidí“ IR přijímač. Průběh tohoto signálu je poměrně „divoký“. Z tohoto důvodu IR čidlo není jen klasický fototranzistor nebo fotodioda, ale je osazeno určitou vyhodnocovací inteligencí (viz obr. 2). Úkolem celého IR čidla tedy je přijatý signál demodulovat a převést ho do podoby binárního průběhu, který pak již můžeme číst jako digitální hodnoty na GPIO vstupu ESP32 (viz obr. 7)

Celý signál má pevně danou strukturu (dle protokolu NEC):
- Na začátku je sestupná hrana a 9 ms dlouhý úsek vysílání (dříve se používal k nastavení zesílení dřívějších IR přijímačů),
- následuje pauza 4,5 ms.
- Dále vysílaný bitový signál se skládá z 562 µs dlouhých „blikacích“ úseků (jakési uvození vysílaného bitu),
- mezi kterými jsou „mezery“, jejichž délka určuje hodnotu vysílaného bitu (viz obr. 8):
- Krátká mezera (∼562 µs) znamená logickou 0.
- Dlouhá mezera (∼1675 µs) znamená logickou 1.
V řadě článků se uvádí, že výstup z IR senzoru, který je už připraven pro načtení mikrokontrolérem, by měl být jako na obrázek č. 8.

Ve skutečnosti je však tento výstup invertovaný! Takže délku „mezery“, která určuje, zda se jedná o zakódovanou hodnotu logické „0“, nebo „1“, budeme načítat jako stav HIGH. (viz obr. 9 – vstupní signál modře, výstupní signál žlutě)

Pomocí měření délky trvání stavu HIGH tedy můžeme jednotlivé bity dekódovat a tím získat původní sériový bitový tok.
Jinými slovy: krátký HIGH signalizuje logickou „0“, dlouhý HIGH odpovídá logické „1“.
Struktura datového rámce (NEC protokol)
Standardní NEC protokol obsahuje 32 bitů. Pokud se jedná o variantu NEC8, platí následující:
- Celá sekvence se skládá z 32 bitů – odeslané informace: adresa a příkaz.
- Adresa (8b) a příkaz (8b) se vysílají dvakrát. V druhé osmici dané informace (adresa/příkaz) jsou všechny bity invertovány a používají se k ověření přijaté zprávy. Pokud se hodnota a hodnota plynoucí z její inverze neshodují, je přenesená sekvence považována za neplatnou a příkaz se ignoruje.
- Bity každého byte se přenášejí od LSB k MSB.
Pokud nemáte zájem o spolehlivost, můžete invertované hodnoty ignorovat nebo můžete adresu a příkaz rozšířit na 16 bitů. Obětováním redundance adres byl rozsah adres rozšířen z 256 možných hodnot na přibližně 65 000 různých hodnot (protokol NEC16). Tímto způsobem byl rozsah adres rozšířen z 8 bitů na 16 bitů, aniž by se změnila jakákoli jiná vlastnost protokolu.
Čistě pro zajímavost zmiňujeme, že se příkaz vysílá pouze jednou – a to i v případě, že je tlačítko na dálkovém ovladači stisknuté po dlouhou dobu. Při tomto trvalém stisknutí se každých 110 ms odvysílá pouze zvláštní opakující se kód, který potvrzuje platnost předchozího příkazu. Tento opakující se kód je 9 ms úvodní impuls, následovaný 2,25 ms mezerou a 560 μs dávkou.
Princip funkce našeho programu:
Podívejme se, co musí náš program udělat, aby dokázal načíst odeslanou sekvenci a vyhodnotit ji jako konkrétní povel, na který následně zareaguje.
Program postupně:
- čeká na začátek IR signálu (tedy na první sestupnou hranu, která označuje start přenosu),
- měří délku trvání vysokých pulsů (HIGH) v mikrosekundách,
- na základě těchto hodnot rekonstruuje binární kód (krátký HIGH znamená 0, dlouhý HIGH znamená 1).
Z takto získané sekvence jedniček a nul pak program pokračuje dál:
- z řetězce tvořeného sekvencí znaků „0“ a „1“ získá příkaz (kód zmáčknutého tlačítka) a případně i adresu (i když tu v našem případě nevyužijeme),
- kód příkazu pak porovná s předem nadefinovanou slovníkovou tabulkou, která obsahuje přiřazení kódů k jednotlivým funkcím (tu si vytvoříme podle konkrétního ovladače).
Když tedy víme, co má program dělat, pojďme si ukázat, jak to celé zrealizovat v MicroPythonu.
1. Čekání na začátek signálu (první LOW):
Pokud není vysílán žádný signál, zůstává výstup modulu KY-022 na úrovni HIGH. Přenos začíná poklesem signálu na úroveň LOW – v tu chvíli se na modulu KY-022 rozsvítí vestavěná LED (viz schéma – obr. 4).
Program tedy nejprve čeká na první sestupnou hranu, která značí začátek přenosu:
wait == 1
while wait == 1:
wait = ird.value()
2. Hlavní měření signálu:
Po začátku přenosu následuje samotná sekvence bitů (0 a 1). Nyní nás zajímá délka trvání stavů HIGH.
a) stavy LOW (signál = 0):
Prvním příchozím stavem je LOW, neboť tím přenos začíná. Také stavy HIGH jsou prokládány krátkým stavem LOW. Tento úsek neměříme – jednoduše ho „přečkáme“ a začneme měřit vždy až délku následujícího stavu HIGH
while ird.value() == 0:
pass
b) Měření délky HIGH (signál = 1):
Jakmile začne stav HIGH, spustíme měření. Uložíme čas začátku a po jeho skončení čas konce. Výsledný rozdíl uložíme do seznamu seq1
.
Pozor: pokud přenos skončí, modul KY-022 zůstane ve stavu HIGH – a cyklus by se nikdy neukončil. Proto kontrolujeme, jestli trvání signálu HIGH nepřesáhne 10 ms. Pokud ano, přenos považujeme za ukončený a smyčku ukončíme pomocí proměnné complete.
seq1 = [] # Délky logických 1 (signál HIGH)
complete = 0
ms1 = utime.ticks_us()
while ird.value() == 1 and complete == 0:
ms2 = utime.ticks_us()
diff = utime.ticks_diff(ms2, ms1)
if diff > 10000:
complete = 1
seq1.append(diff)
3. Převod měřených délek na binární kód:
Ze změřených délek stavu HIGH zrekonstruujeme binární řetězec. Krátký puls značí logickou „0“, delší značí „1“. Současně odfiltrujeme poslední stav, který přenos ukončuje (je výrazně delší než ostatní a přidal se díky complete
).
- < 700 µs → logická 0
- 700–2000 µs → logická 1
Výsledná binární sekvence logických „0“ a „1“ se ukládá do textového řetězce code. Protože se přenáší nejdříve nejnižší bit (LSB) sestavujeme řetězec zprava doleva.
bin_code = ""
for val in seq1:
if val < 2000:
if val < 700:
bin_code = "0" + bin_code
else:
bin_code = "1" + bin_code
return bin_code
Tím máme definitivně získanou zachycenou binární sekvenci, tak, jak byla odeslána.
4. Převod binárního kódu na příkaz:
Pokud jsme stiskli např. tlačítko 1
, chceme, aby se ve výstupu programu objevila tato informace, tedy jako konkrétní znak „1“. K tomu bude sloužit další část kódu.
Nejprve zkontrolujeme, že délka binárního řetězce je přesně 32 bitů (NEC protokol). Pokud ne, vrátíme hodnoty 0 a 0. Jinak rozdělíme řetězec na příkaz a adresu. Kvůli způsobu načítání (kód jsme otočili už při samotném záznamu) máme segmenty přeházené oproti klasickému NEC8:
- 8.–16. bit obsahuje příkaz (data)
- 24.–32. bit obsahuje adresu
def parse_nec_code(bin_str):
if len(bin_str) != 32:
print("Chyba: Řetězec musí mít přesně 32 bitů.")
return 0, 0
# pořadí segmentů
data_bin = bin_str[8:16]
addr_bin = bin_str[24:32]
Máme-li správně získané části řetězce, můžeme je převést z binární soustavy na celé číslo:
# Převedení do int
data = int(data_bin, 2) # ta 2 oznacuje ciselnou soustavu
addr = int(addr_bin, 2)
Získané výsledky funkce vrátí.
# Výpis výsledků
return data, addr
5. Přiřazení příkazu pomocí slovníku
Získaný číselný kód (např. 247) přiřadíme k názvu tlačítka pomocí předem nadefinovaného slovníku act
. Pokud kód ve slovníku nemáme, vracíme „nedef.“:
def tlacitko(code):
command = ""
for k,v in act.items():
if code == k:
command = v
if command == "":
command = "nedef."
return command
import utime
from machine import Pin
ird = Pin(15, Pin.IN)
act = {
69 : "1",
70 : "2",
71 : "3",
68 : "4",
64 : "5",
67 : "6",
7 : "7",
21 : "8",
9 : "9",
25 : "0",
28 : "OK",
24 : "UP",
82 : "DOWN",
8 : "LEFT",
90 : "RIGHT",
22 : "*",
13 : "#"
}
def read_ircode(ird):
wait = 1
complete = 0
seq1 = []
# cekame na sestupnou hranu
while wait == 1:
wait = ird.value()
while complete == 0:
# cekame na nabeznou hranu
while ird.value() == 0:
pass
# merime cas stavu HIGH
ms1 = utime.ticks_us()
while ird.value() == 1 and complete == 0:
ms2 = utime.ticks_us()
diff = utime.ticks_diff(ms2, ms1)
if diff > 50000:
complete = 1
seq1.append(diff)
bin_code = ""
for val in seq1:
if val < 2000:
if val < 700:
bin_code = "0" + bin_code
else:
bin_code = "1" + bin_code
# print(bin_code)
return bin_code
def tlacitko(code):
command = ""
for k, v in act.items():
if code == k:
command = v
if command == "":
command = "nedef."
return command
def parse_nec_code(bin_str):
if len(bin_str) != 32:
print("Chyba: Řetězec musí mít přesně 32 bitů.")
return 0, 0
# pořadí segmentů
data_bin = bin_str[8:16]
addr_bin = bin_str[24:32]
# Převedení do integerů
data = int(data_bin, 2)
addr = int(addr_bin, 2)
# Výpis výsledků
return data, addr
while True:
result = read_ircode(ird)
data, addr = parse_nec_code(result)
print(f"\nbinarni sekv.: {result}")
print(f"Data: 0x{data:02x}, Addr: 0x{addr:02x}")
print("Tlacitko:", tlacitko(data))
utime.sleep(0.5)

Po spuštění kódu se program v modulu ESP32 ihned zastaví na funkci read_ircode()
, která čeká na příjem IR signálu. Jakmile na dálkovém ovladači stiskneme jakékoliv tlačítko, je zachycena a zpracována celá odeslaná sekvence. Funkce read_ircode()
tuto sekvenci vrátí jako binární řetězec.
Pomocí funkce parse_nec_code()
z něj následně získáme adresu a příkaz. Ty jsou zpracovány podle předpokládaného protokolu NEC8, tedy s bitovou adresou. Obě hodnoty se převedou na celá čísla a vypíšou ve formátu hexadecimálních čísel.
Pro praktické použití je ale mnohem pohodlnější, když místo čísel vidíme přímo označení stisknutého tlačítka. K tomu slouží předem nadefinovaný slovník a funkce tlacitko()
, která podle kódu vrátí textový popis.
Kód, jak je zde uvedený, není zamýšlen jako produkční, ale spíše jako didaktický příklad. Víme, že by šlo algoritmus optimalizovat – například dekódovat jednotlivé bity rovnou během měření a neukládat nejprve celé pole časů. To už ale ponecháváme na zvídavosti a experimentování čtenářů, kteří si mohou kód upravit podle svého.
Výstup programu vidíme na následujícím obrázku č. 11:

Pokud použijeme jiné dálkové ovládání (například od televize), program sice na stisknutá tlačítka zareaguje, ale nezobrazí jejich názvy – nemáme je totiž zahrnuté v našem převodním slovníku. Pro plnohodnotné použití bychom si museli tabulku příkazů doplnit sami podle konkrétního ovladače.
Načítání IR čidla knihovnou:
Přestože jsme si sami dokázali přečíst kód stisknutého tlačítka z IR ovladače, má náš původní program jednu výraznou slabinu. Funkce read_ircode()
zastaví běh programu a čeká na příchod signálu – konkrétně na první sestupnou hranu. A ta může přijít… ale také nemusí. Pokud dálkový ovladač právě nikdo nepoužívá, je ESP32 zcela paralyzovaný a nedělá vůbec nic.
Pokud chceme ovladač použít pro řízení nějaké „užitečné“ funkce (např. spouštění akce nebo přepínání režimu), potřebujeme, aby ESP32 zůstalo aktivní a reagovalo na signál jen tehdy, když skutečně přijde.
Řešení jsou v zásadě dvě:
- celý původní přístup přepracovat (např. pomocí přerušení),
- nebo sáhnout po hotovém řešení v podobě knihovny, která to za nás již vyřešila.
Jednou z možností je knihovna ir_rx.py
od Petera Hinche (🔗 GitHub – micropython_ir),
která nabízí komfortní a asynchronní zpracování IR signálu. Podle GitHubu je její poslední verze z roku 2024, což je skvělá zpráva – autor ji stále udržuje aktuální.
Z praxe víme, že u neaktualizovaných knihoven starších pěti a více let často dochází k problémům s kompatibilitou, zejména s novějšími verzemi MicroPythonu pro ESP32. Tady by nás to ale díky aktivnímu vývoji nemělo potkat.
Hlavní předností použití knihovny je však pochopitelně v tom pohodlí, kdy nemusíme řešit nějaké stavu LOW a HIGH plynoucí z protokolu NEC, a prostě jen načíst čidlo zvolenou metodou. To je někdy fakt k nezaplacení!
Problém s knihovnou ir_rx.py
Bohužel – a jak už to u ESP32 s MicroPythonem bývá – ani knihovna ir_rx.py
nefunguje hned napoprvé tak, jak bychom očekávali.
Po stažení všech potřebných souborů a jejich nahrání do ESP32 program selže. Po důkladnějším prozkoumání zdrojového kódu jsme zjistili, že problém nastává při pokusu knihovny použít tzv. softwarový časovač. Zatímco hardwarové časovače modulu ESP32 dobře známe – používají se například pro přerušení nebo generování PWM signálu – o existenci softwarového časovače v MicroPythonu slyšíme v této souvislosti poprvé. A zřejmě nejsme sami. Zdá se, že ani náš konkrétní firmware ESP32 o takové možnosti netuší – a přesně na tom knihovna hlásí chybu.
Je možné, že v některé starší verzi MicroPythonu, nebo na jiném typu firmwaru, tato konstrukce fungovala. Ale zde jsme narazili.
Abychom mohli v článku pokračovat a zároveň čtenářům nabídli prakticky použitelný kód, rozhodli jsme se knihovnu upravit. Vytvořili jsme zjednodušenou verzi, kterou lze použít jako lehký a přehledný modul v jednom souboru – ideální pro naše účely. Věříme, že autor knihovny na GitHubu brzy vydá aktualizovanou verzi s podporou i pro aktuální buildy ESP32, kde tento problém již nebude.
ir_rx.py
(verze FyzKAB):
# Author: Peter Hinch
# temporary version 2025 – modified by FyzKAB (fixed problem with Timer(-1) for ESP32)
# Copyright Peter Hinch 2020-2024 Released under the MIT license
# Thanks are due to @Pax-IT for diagnosing a problem with ESP32C3.
from machine import Timer, Pin
from array import array
from utime import ticks_us, ticks_diff
# from micropython import alloc_emergency_exception_buf
# alloc_emergency_exception_buf(100)
# On 1st edge start a block timer. While the timer is running, record the time
# of each edge. When the timer times out decode the data. Duration must exceed
# the worst case block transmission time, but be less than the interval between
# a block start and a repeat code start (~108ms depending on protocol)
class IR_RX:
Timer_id = 2 # Software timer but enable override
# Result/error codes
# Repeat button code
REPEAT = -1
# Error codes
BADSTART = -2
BADBLOCK = -3
BADREP = -4
OVERRUN = -5
BADDATA = -6
BADADDR = -7
def __init__(self, pin, nedges, tblock, callback, *args): # Optional args for callback
self._pin = pin
self._nedges = nedges
self._tblock = tblock
self.callback = callback
self.args = args
self._errf = lambda _: None
self.verbose = False
self._times = array("i", (0 for _ in range(nedges + 1))) # +1 for overrun
pin.irq(handler=self._cb_pin, trigger=(Pin.IRQ_FALLING | Pin.IRQ_RISING))
self.edge = 0
self.tim = Timer(self.Timer_id) # Defaul is sofware timer
self.cb = self.decode
# Pin interrupt. Save time of each edge for later decode.
def _cb_pin(self, line):
t = ticks_us()
# On overrun ignore pulses until software timer times out
if self.edge <= self._nedges: # Allow 1 extra pulse to record overrun
if not self.edge: # First edge received
self.tim.init(period=self._tblock, mode=Timer.ONE_SHOT, callback=self.cb)
self._times[self.edge] = t
self.edge += 1
def do_callback(self, cmd, addr, ext, thresh=0):
self.edge = 0
if cmd >= thresh:
self.callback(cmd, addr, ext, *self.args)
else:
self._errf(cmd)
def error_function(self, func):
self._errf = func
def close(self):
self._pin.irq(handler=None)
self.tim.deinit()
class NEC_ABC(IR_RX):
def __init__(self, pin, extended, samsung, callback, *args):
# Block lasts <= 80ms (extended mode) and has 68 edges
super().__init__(pin, 68, 80, callback, *args)
self._extended = extended
self._addr = 0
self._leader = 2500 if samsung else 4000 # 4.5ms for Samsung else 9ms
def decode(self, _):
try:
if self.edge > 68:
raise RuntimeError(self.OVERRUN)
width = ticks_diff(self._times[1], self._times[0])
if width < self._leader: # 9ms leading mark for all valid data
raise RuntimeError(self.BADSTART)
width = ticks_diff(self._times[2], self._times[1])
if width > 3000: # 4.5ms space for normal data
if self.edge < 68: # Haven't received the correct number of edges
raise RuntimeError(self.BADBLOCK)
# Time spaces only (marks are always 562.5µs)
# Space is 1.6875ms (1) or 562.5µs (0)
# Skip last bit which is always 1
val = 0
for edge in range(3, 68 - 2, 2):
val >>= 1
if ticks_diff(self._times[edge + 1], self._times[edge]) > 1120:
val |= 0x80000000
elif width > 1700: # 2.5ms space for a repeat code. Should have exactly 4 edges.
raise RuntimeError(self.REPEAT if self.edge == 4 else self.BADREP) # Treat REPEAT as error.
else:
raise RuntimeError(self.BADSTART)
addr = val & 0xff # 8 bit addr
cmd = (val >> 16) & 0xff
if cmd != (val >> 24) ^ 0xff:
raise RuntimeError(self.BADDATA)
if addr != ((val >> 8) ^ 0xff) & 0xff: # 8 bit addr doesn't match check
if not self._extended:
raise RuntimeError(self.BADADDR)
addr |= val & 0xff00 # pass assumed 16 bit address to callback
self._addr = addr
except RuntimeError as e:
cmd = e.args[0]
addr = self._addr if cmd == self.REPEAT else 0 # REPEAT uses last address
# Set up for new data burst and run user callback
self.do_callback(cmd, addr, 0, self.REPEAT)
class NEC_8(NEC_ABC):
def __init__(self, pin, callback, *args):
super().__init__(pin, False, False, callback, *args)
class NEC_16(NEC_ABC):
def __init__(self, pin, callback, *args):
super().__init__(pin, True, False, callback, *args)
Neztrácejme čas – pojďme rovnou na příklad s knihovnou ir_rx.py
Než ztrácet čas láteřením nad problémy s knihovnami v MicroPythonu pro modul ESP32 raději se rovnou pustíme do ukázky použití knihovny ir_rx.py
.
Použití knihovny ir_rx.py
Následující příklad ilustruje nejen použití samotné knihovny ir_rx.py
, ale hlavně její největší výhodu – asynchronní zpracování IR signálu. Zatímco v hlavní smyčce programu bude blikat vestavěnou LED, IR přijímač mezitím bude na pozadí a nezávisle detekovat příchozí signály z dálkového ovladače.
import time
from machine import Pin
from ir_rx import NEC_8
def callback(data, addr, ctrl):
if data > 0: # NEC protocol sends repeat codes.
print('Data {:02x}'.format(data))
ir = NEC_16(Pin(15, Pin.IN), callback)
led = Pin(2, Pin.OUT)
print('LED blika...')
while True:
led.on()
time.sleep(0.1)
led.off()
time.sleep(0.1)
Pro načítání IR čidla potřebujeme z knihovny ir_rx.py
importovat třídu NEC_8
,
která odpovídá protokolu použitého v modulu KY-022:
from ir_rx import NEC_8
Vytváříme instanci třídy NEC_8
, kde:
- první parametr je GPIO pin (zde
Pin(15)
), na který je připojen signálový výstup IR modulu, - druhý parametr je funkce
callback
, která se zavolá pokaždé, když čidlo zachytí kód z ovladače.
ir = NEC_8(Pin(15, Pin.IN), callback)
Funkce callback()
přijímá tři parametry, ale pro začátek nás zajímá jen ten data, tedy samotný kód přijatý z IR ovladače. Pokud je hodnota větší než nula (což vylučuje opakované kódy NEC protokolu), vypíšeme ji na výstup:
def callback(data, addr, ctrl):
if data > 0: # NEC protocol sends repeat codes.
print('Data {:02x}'.format(data))
Tím máme funkční IR čidlo! Zbytek programu tvoří jednoduchá nekonečná smyčka, ve které bliká LED – jako simulace jiné paralelní činnosti.
Následující okno prostředí Thonny IDE ukazuje výstup programu:

ir_rx.py
(výpis kódů)Převod kódu na název tlačítka
Podobně jako v dřívějším příkladu můžeme převést přijatý číselný kód na popis stisknutého tlačítka. Stačí si nadefinovat „slovník“, ve kterém bude každému číslu odpovídat konkrétní tlačítko:
ir_key = {
0x1C: 'OK',
0x5A: 'RIGHT',
0x08: 'LEFT',
0x52: 'DOWN',
0x18: 'UP',
0x0D: '#',
0x16: '*',
0x19: '0',
0x45: '1',
0x46: '2',
0x47: '3',
0x44: '4',
0x40: '5',
0x43: '6',
0x07: '7',
0x15: '8',
0x09: '9'
}
Výše uvedené hodnoty jsou pochopitelně nastaveny pro námi použité IR dálkové ovládání.
Funkci callback()
pak jednoduše upravíme, aby vytiskla název odpovídajícího tlačítka:
def callback(data, addr, ctrl):
if data > 0:
print(ir_key.get(data, "nedef."))
- Poznámka:
- Použili jsme metodu
.get()
místo klasického indexování, abychom předešli chybě, pokud přijde neznámý kód, v takovém případě se vypíšenedef.
Obrázek č. 13 ukazuje výpis výstupu programu v okně prostředí Thonny IDE:

ir_rx.py
(výpis znaků)IR ovládání vestavěné LED
A do třetice všeho dobrého – když už nám detekce tlačítek funguje spolehlivě, zkusíme vytvořit program, který bude i trochu praktický. Navíc si na něm ukážeme jednu nenápadnou past, kterou jsme omylem nastražili naší opravou knihovny ir_rx.py
.
- Poznámka:
- Inspirací pro tento příklad byl projekt George Bantiqua ze stránek
TechToTinker.
.blogspot. com
Co program dělá?
Pomocí dálkového ovladače budeme ovládat vestavěnou LED na modulu ESP32. Program rozpozná tři povely:
- Tlačítko
1
– LED se rozsvítí. - Tlačítko
0
– LED zhasne. - Tlačítko
OK
– LED začne blikat.
Změna oproti předchozímu příkladu spočívá hlavně v úpravě hlavní smyčky, která se bude řídit podle příkazu přijatého z IR ovladače.
Hlavní smyčka – řízení LED
Než si ukážeme celý kód, pojďme se podívat na samotnou část smyčky, která vykonává dané akce:
while True:
if ir_data > 0:
if ir_data == 0x19: # 0
led.value(0)
if isLedBlinking == True:
tim1.deinit()
isLedBlinking = False
elif ir_data == 0x45: # 1
led.value(1)
if isLedBlinking == True:
tim1.deinit()
isLedBlinking = False
elif ir_data == 0x1C: # OK
isLedBlinking = True
tim1.init(period= 500, mode= Timer.PERIODIC, callback= timer_callback)
ir_data = 0
V hlavní smyčce budeme testovat proměnnou ir_data
, která bude obsahovat kód naposledy stisknutého tlačítka na IR ovladači.
Tato proměnná bude:
- globální, protože ji budeme nastavovat uvnitř funkce
callback()
- použita v hlavní smyčce pro rozhodování, co dělat s LED (rozsvítit, zhasnout, nebo spustit blikání)
Úprava funkce callback()
Změníme ji tak, aby kromě výpisu také ukládala hodnotu do ir_data
:
def ir_callback(data, addr, ctrl):
global ir_data
if data > 0:
ir_data = data
print('Data {:02x}'.format(data))
Reakce na tlačítka
V hlavní smyčce následně testujeme obsah proměnné ir_data
. Pro tlačítka:
1
(kód0x45
) – rozsvítíme LED0
(kód0x19
) – LED zhasnemeOK
(kód0x1C
) – spustíme blikání pomocí časovače
Blikání pomocí přerušení – funkce časovače
Pro blikání použijeme časovač, který bude každých 500 ms měnit stav LED pomocí funkce timer_callback()
:
def timer_callback(timer):
led.value(not led.value())
Funkce každým zavoláním přepne hodnotu pinu (z 0 na 1 a zpět), čímž vytvoří efekt blikání.
Když je stisknuto tlačítko 0
nebo 1
, program:
- nejprve zkontroluje, zda časovač běží (indikováno proměnnou
isLedBlinking
) - pokud ano, časovač zastaví pomocí
tim1.deinit()
- LED buď rozsvítí, nebo zhasne podle konkrétního příkazu
from machine import Pin
from machine import Timer
from ir_rx import NEC_8
def ir_callback(data, addr, ctrl):
global ir_data
if data > 0:
ir_data = data
print('Data {:02x}'.format(data))
def timer_callback(timer):
led.value(not led.value())
ir = NEC_8(Pin(15, Pin.IN), ir_callback)
led = Pin(2, Pin.OUT)
tim1 = Timer(1)
isLedBlinking = False
ir_data = 0
while True:
if ir_data > 0:
if ir_data == 0x19: # 0
led.value(0)
if isLedBlinking == True:
tim1.deinit()
isLedBlinking = False
elif ir_data == 0x45: # 1
led.value(1)
if isLedBlinking == True:
tim1.deinit()
isLedBlinking = False
elif ir_data == 0x1C: # OK
isLedBlinking = True
tim1.init(period=500, mode=Timer.PERIODIC, callback=timer_callback)
ir_data = 0
- Poznámky:
- Než se definitivně rozloučíme s tímto tématem, podívejme se na dvě drobnosti, které mohou způsobit zbytečné potíže:
1. Globální proměnná ir_data
Ve funkci callback()
měníme hodnotu proměnné ir_data
. Aby to fungovalo správně, musíme tuto proměnnou uvnitř funkce označit jako globální. Jinak by Python vytvořil svou vlastní lokální kopii a hlavní program by o změně vůbec nevěděl.
Správný zápis vypadá tedy takto:
def ir_callback(data, addr, ctrl):
global ir_data
if data > 0:
ir_data = data
print('Data {:02x}'.format(data))
2. Problém Timer(2)
– naše vlastní past
A teď slíbená past – schválně si ji můžete vyzkoušet:
V původním programu jsme použili hardwarový časovač Timer(1)
. Co se stane, když v kódu časovač změníte třeba na Timer(2)
?
tim1 = Timer(2)
Program se sice spustí, tlačítka dál fungují… Skoro! Přestalo nám fungovat blikání LED (tlačítko OK
).
Vysvětlení: Kolize Timer(2)
Narazili jsme zde na konflikt mezi vlastními funkcemi a knihovnou ir_rx.py
. V původní verzi knihovny autor použil softwarový časovač Timer(-1)
(někdy označovaný jako „virtuální“), který však nefunguje na všech verzích MicroPythonu pro ESP32. A protože jsme chtěli, aby knihovna fungovala spolehlivě i na aktuálních verzích, museli jsme ji opravit – a v naší upravené verzi ir_rx.py
jsme místo toho zvolili hardwarový časovač Timer(2)
. To ale
znamená, že knihovna ir_rx.py
nyní interně využívá Timer(2)
pro svou vlastní práci s IR signálem.
Pokud tedy v našem programu Timer(2)
použijeme znovu – například pro blikání LED nebo jiné časově řízené operace – dojde ke kolizi. Výsledkem je, že část programu (např. blikání LED) přestane fungovat, protože časovač už je vázán na knihovnu.
Dopad na ostatní funkce
Naše úprava knihovny tedy obsadila Timer(2)
. To má i další důsledky:
- Nelze použít PWM na některých pinech, které sdílejí časovač 2 – konkrétně jde o
GPIO10
aGPIO12
- Jakákoli další práce s
Timer(2)
bude problém a nejspíše nefunkční
Co s tím?
Musíme si tedy pamatovat, že Timer(2)
je „obsazený“ knihovnou ir_rx.py
, a pokud v programu potřebujeme:
- PWM výstup
- vlastní časovače
…musíme je vázat na jiné časovače: Timer(0)
, Timer(1)
nebo Timer(3)
. To by mělo být pro běžné projekty více než dostačující.
A nebo, pokud chceme úplnou volnost, můžeme zkusit jiný firmware nebo pozdější verzi knihovny (autor Peter Hinch knihovnu pravidelně aktualizuje, takže časem může být vše opět jinak).
Závěr:
Původně jsme plánovali věnovat se v tomto článku jak přijímání, tak i vysílání IR signálu. Projekt se ale komplikoval
nefunkčností původní knihovny ir_rx.py
, proto jsme se zatím soustředili pouze na IR přijímač.
Co jsme si tedy dnes odnesli:
- Pochopili jsme základní principy infračervené (IR) komunikace, kde se data přenáší pomocí pulzů světla nesených na nosné frekvenci (typicky kolem 38 kHz).
- Seznámili jsme se s protokolem NEC, jedním z nejrozšířenějších protokolů pro IR dálkové ovládání, který zahrnuje přenos adresy zařízení a příkazů ve specifické časové posloupnosti impulsů.
- Vyzkoušeli jsme si přímé načítání IR signálu bez použití specializované knihovny – „ruční“ zpracování časů trvání pulsů a jejich dekódování do binární podoby.
- Naučili jsme se využívat knihovnu
ir_rx.py
, která nabízí asynchronní detekci signálů pomocí callback funkcí a výrazně tak zjednodušuje práci s IR. - Díky opravě knihovny jsme zjistili, že modul ESP32 má omezený počet hardwarových časovačů a je třeba dávat pozor na kolize. Upravená knihovna
ir_rx.py
totiž nyní používáTimer(2)
, což může způsobit problémy s dalšími funkcemi (například PWM).
Při objevení knihovny ir_rx.py
jsme narazili i na její „sestřičku“ ir_tx.py
, která umožňuje přeměnit IR LED diodu připojenou k modulu ESP32 na dálkové ovládání – tedy vysílač IR signálu! Naše IR sada, kterou jsme při dnešních hrátkách používali, obsahuje nejen dálkové ovládání a IR přijímač KY-022, ale právě i IR LED. Jak jsme na začátku říkali, že si ji schováme na další pokusy. Tak to by mohlo být ono! 😊
Do rukou se nám také dostala barevná RGB žárovka ovladatelná IR dálkovým ovladačem. To je výzva pro každého bastlíře! Představa, jak můžeme s pomocí modulu ESP32 a MicroPythonu dálkově ovládat barevné osvětlení nad pracovním stolem, je opravdu takovým „vánočním dárkem… v červnu“!
Příště nás tedy čekají hrátky s knihovnou ir_tx.py
– a hlavně pořádná zábava s ovládáním RGB žárovky! Samozřejmě, pokud někde zase nečíhá nějaké „prohnilé“ překvapení, jak tomu bylo už párkrát při pokusu o použití cizí hotové knihovny.