044 - MicroPython TechNotes: Infrared Receiver

Introduction

In this article, I will tackle how you can use an Infrared receiver module with ESP32 using MicroPython. GorillaCell Infrared Receiver module from GorillaCell ESP32 development kits uses a VS1838 photodiode infrared receiver. It is low cost and easy to use.

Bill Of Materials

  1. ESP32 development board.
  2. Gorillacell ESP32 shield.
  3. 3-pin female-female dupont wires.
  4. Gorillacell Infrared Receiver module.

Pinout

It has 3 pins namely:

  1. G – for the ground pin.
  2. V – for the supply voltage.
  3. S – for the infrared receive signal pin.

Hardware Instruction

  1. First, attach the ESP32 development board on top of the ESP32 shield and make sure that both the USB port are on the same side.
  2. Next, attach the dupont wires to the Infrared Receiver module by following the color coding that is black for the ground, red for the VCC, and yellow for the signal pin.
  3. Next, attach the other end of the dupont wires to the ESP32 shield by matching the colors of the wires to the colors of the pin headers that is black is to black, red is to red, and yellow is to yellow pin headers. For this lesson, I choose GPIO 23 to serve as the input pin from the Infrared Receiver module.
  4. Next, power the ESP32 shield with an external power supply with a type-C USB connector and make sure that the power switch is set to ON state.
  5. Lastly, connect the ESP32 to the computer using a micro-USB connector cable.

Software Instruction

  1. Copy the ir_rx.py from the SOURCE CODE section and paste it to Thonny IDE.
  2. Save it to MicroPython root directory by clicking the File menu and select Save As.
  3. Select MicroPython Device.
  4. And save it as ir_rx.py.
  5. Copy other examples to Thonny IDE and run it accordingly.
  6. Feel free to modify and adapt according to your needs.

Video Demonstration

Call To Action

If you have any concern regarding this video, please write your question in the comment box.

You might also liked to support my journey on Youtube by subscribing on my channel, TechToTinker. Click this to Subscribe.

Thank you and have a good days ahead.

– George Bantique | tech.to.tinker@gmail.com

Source Code

1. Example # 1, decode and display the receive infrared data to the REPL:

 1# More details can be found in TechToTinker.blogspot.com 
 2# George Bantique | tech.to.tinker@gmail.com
 3
 4import time
 5from machine import Pin
 6from ir_rx import NEC_16
 7
 8def callback(data, addr, ctrl):
 9    if data > 0:  # NEC protocol sends repeat codes.
10        print('Data {:02x} Addr {:04x}'.format(data, addr))
11
12ir = NEC_16(Pin(23, Pin.IN), callback)

2. Example # 2, display actual buttons key in the REPL:

 1# More details can be found in TechToTinker.blogspot.com 
 2# George Bantique | tech.to.tinker@gmail.com
 3
 4from machine import Pin
 5from ir_rx import NEC_16
 6
 7ir_key = {
 8    0x45: 'POWER',
 9    0x46: 'MODE',
10    0x47: 'MUTE',
11    0x44: 'PLAY',
12    0x40: 'PREV',
13    0x43: 'NEXT',
14    0x07: 'EQ',
15    0x15: 'MINUS',
16    0x09: 'PLUS',
17    0x16: '0',
18    0x19: 'REPEAT',
19    0x0D: 'USD',
20    0x0C: '1',
21    0x18: '2',
22    0x5E: '3',
23    0x08: '4',
24    0x1C: '5',
25    0x5A: '6',
26    0x42: '7',
27    0x52: '8',
28    0x4A: '9'    
29    }
30
31def callback(data, addr, ctrl):
32    if data > 0:  # NEC protocol sends repeat codes.
33        #print('Data {:02x} Addr {:04x}'.format(data, addr))
34        print(ir_key[data])
35
36ir = NEC_16(Pin(23, Pin.IN), callback)
 1# More details can be found in TechToTinker.blogspot.com 
 2# George Bantique | tech.to.tinker@gmail.com
 3
 4from machine import Pin
 5from machine import Timer
 6from ir_rx import NEC_16
 7
 8
 9def ir_callback(data, addr, ctrl):
10    global ir_data
11    global ir_addr
12    if data > 0:
13        ir_data = data
14        ir_addr = addr
15        print('Data {:02x} Addr {:04x}'.format(data, addr))
16            
17def timer_callback(timer):
18    led.value( not led.value() )        
19
20ir = NEC_16(Pin(23, Pin.IN), ir_callback)
21led = Pin(2, Pin.OUT)
22tim0 = Timer(0)
23isLedBlinking = False
24ir_data = 0
25ir_addr = 0
26
27while True:
28    if ir_data > 0:
29        if ir_data == 0x16:   # 0
30            led.value(0)
31            if isLedBlinking==True:
32                tim0.deinit()
33                isLedBlinking = False
34        elif ir_data == 0x0C: # 1
35            led.value(1)
36            if isLedBlinking==True:
37                tim0.deinit()
38                isLedBlinking = False
39        elif ir_data == 0x18: # 2
40            isLedBlinking = True
41            tim0.init(period=500,
42                      mode=Timer.PERIODIC,
43                      callback=timer_callback)
44        ir_data = 0

4. micropython_ir.py driver library:

  1# MIT License
  2# 
  3# Copyright (c) 2020 Peter Hinch
  4# 
  5# Permission is hereby granted, free of charge, to any person obtaining a copy
  6# of this software and associated documentation files (the "Software"), to deal
  7# in the Software without restriction, including without limitation the rights
  8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9# copies of the Software, and to permit persons to whom the Software is
 10# furnished to do so, subject to the following conditions:
 11# 
 12# The above copyright notice and this permission notice shall be included in all
 13# copies or substantial portions of the Software.
 14# 
 15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 21# SOFTWARE.
 22
 23# Author: Peter Hinch
 24# Copyright Peter Hinch 2020-2021 Released under the MIT license
 25# http://github.com/peterhinch/micropython_ir
 26
 27from machine import Pin
 28from machine import Timer
 29from array import array
 30from utime import ticks_us
 31from utime import ticks_diff
 32
 33# Save RAM
 34# from micropython import alloc_emergency_exception_buf
 35# alloc_emergency_exception_buf(100)
 36
 37
 38# On 1st edge start a block timer. While the timer is running, record the time
 39# of each edge. When the timer times out decode the data. Duration must exceed
 40# the worst case block transmission time, but be less than the interval between
 41# a block start and a repeat code start (~108ms depending on protocol)
 42
 43class IR_RX():
 44    # Result/error codes
 45    # Repeat button code
 46    REPEAT = -1
 47    # Error codes
 48    BADSTART = -2
 49    BADBLOCK = -3
 50    BADREP = -4
 51    OVERRUN = -5
 52    BADDATA = -6
 53    BADADDR = -7
 54
 55    def __init__(self, pin, nedges, tblock, callback, *args):  # Optional args for callback
 56        self._pin = pin
 57        self._nedges = nedges
 58        self._tblock = tblock
 59        self.callback = callback
 60        self.args = args
 61        self._errf = lambda _ : None
 62        self.verbose = False
 63
 64        self._times = array('i',  (0 for _ in range(nedges + 1)))  # +1 for overrun
 65        pin.irq(handler = self._cb_pin, trigger = (Pin.IRQ_FALLING | Pin.IRQ_RISING))
 66        self.edge = 0
 67        self.tim = Timer(-1)  # Sofware timer
 68        self.cb = self.decode
 69
 70    # Pin interrupt. Save time of each edge for later decode.
 71    def _cb_pin(self, line):
 72        t = ticks_us()
 73        # On overrun ignore pulses until software timer times out
 74        if self.edge <= self._nedges:  # Allow 1 extra pulse to record overrun
 75            if not self.edge:  # First edge received
 76                self.tim.init(period=self._tblock , mode=Timer.ONE_SHOT, callback=self.cb)
 77            self._times[self.edge] = t
 78            self.edge += 1
 79
 80    def do_callback(self, cmd, addr, ext, thresh=0):
 81        self.edge = 0
 82        if cmd >= thresh:
 83            self.callback(cmd, addr, ext, *self.args)
 84        else:
 85            self._errf(cmd)
 86
 87    def error_function(self, func):
 88        self._errf = func
 89
 90    def close(self):
 91        self._pin.irq(handler = None)
 92        self.tim.deinit()
 93
 94
 95class NEC_ABC(IR_RX):
 96    def __init__(self, pin, extended, callback, *args):
 97        # Block lasts <= 80ms (extended mode) and has 68 edges
 98        super().__init__(pin, 68, 80, callback, *args)
 99        self._extended = extended
100        self._addr = 0
101
102    def decode(self, _):
103        try:
104            if self.edge > 68:
105                raise RuntimeError(self.OVERRUN)
106            width = ticks_diff(self._times[1], self._times[0])
107            if width < 4000:  # 9ms leading mark for all valid data
108                raise RuntimeError(self.BADSTART)
109            width = ticks_diff(self._times[2], self._times[1])
110            if width > 3000:  # 4.5ms space for normal data
111                if self.edge < 68:  # Haven't received the correct number of edges
112                    raise RuntimeError(self.BADBLOCK)
113                # Time spaces only (marks are always 562.5µs)
114                # Space is 1.6875ms (1) or 562.5µs (0)
115                # Skip last bit which is always 1
116                val = 0
117                for edge in range(3, 68 - 2, 2):
118                    val >>= 1
119                    if ticks_diff(self._times[edge + 1], self._times[edge]) > 1120:
120                        val |= 0x80000000
121            elif width > 1700: # 2.5ms space for a repeat code. Should have exactly 4 edges.
122                raise RuntimeError(self.REPEAT if self.edge == 4 else self.BADREP)  # Treat REPEAT as error.
123            else:
124                raise RuntimeError(self.BADSTART)
125            addr = val & 0xff  # 8 bit addr
126            cmd = (val >> 16) & 0xff
127            if cmd != (val >> 24) ^ 0xff:
128                raise RuntimeError(self.BADDATA)
129            if addr != ((val >> 8) ^ 0xff) & 0xff:  # 8 bit addr doesn't match check
130                if not self._extended:
131                    raise RuntimeError(self.BADADDR)
132                addr |= val & 0xff00  # pass assumed 16 bit address to callback
133            self._addr = addr
134        except RuntimeError as e:
135            cmd = e.args[0]
136            addr = self._addr if cmd == self.REPEAT else 0  # REPEAT uses last address
137        # Set up for new data burst and run user callback
138        self.do_callback(cmd, addr, 0, self.REPEAT)
139
140class NEC_8(NEC_ABC):
141    def __init__(self, pin, callback, *args):
142        super().__init__(pin, False, callback, *args)
143
144class NEC_16(NEC_ABC):
145    def __init__(self, pin, callback, *args):
146        super().__init__(pin, True, callback, *args)

References And Credits

  1. Purchase your Gorillacell ESP32 development kits from: https://gorillacell.kr/

  2. Peter Hinch micropython IR library: http://github.com/peterhinch/micropython_ir



Posts in this series



No comments yet!

GitHub-flavored Markdown & a sane subset of HTML is supported.