]> vault307.fbx.one Git - micorpython_ir.git/blob - ir_rx.py
896d9cdb4c5269931d90e77baf040dae509a1102
[micorpython_ir.git] / ir_rx.py
1 # ir_rx.py Decoder for IR remote control using synchronous code
2 # Supports RC-5 RC-6 mode 0 and NEC protocols.
3 # For a remote using NEC see https://www.adafruit.com/products/389
4
5 # Author: Peter Hinch
6 # Copyright Peter Hinch 2020 Released under the MIT license
7
8 from sys import platform
9 from micropython import const
10 from machine import Timer
11 from array import array
12 from utime import ticks_us, ticks_diff
13
14 if platform == 'pyboard':
15 from pyb import Pin, ExtInt
16 else:
17 from machine import Pin
18
19 ESP32 = platform == 'esp32' or platform == 'esp32_LoBo'
20
21 # Save RAM
22 # from micropython import alloc_emergency_exception_buf
23 # alloc_emergency_exception_buf(100)
24
25 # Result codes (accessible to application)
26 # Repeat button code
27 REPEAT = -1
28 # Error codes
29 BADSTART = -2
30 BADBLOCK = -3
31 BADREP = -4
32 OVERRUN = -5
33 BADDATA = -6
34 BADADDR = -7
35
36
37 # On 1st edge start a block timer. While the timer is running, record the time
38 # of each edge. When the timer times out decode the data. Duration must exceed
39 # the worst case block transmission time, but be less than the interval between
40 # a block start and a repeat code start (~108ms depending on protocol)
41
42 class IR_RX():
43 def __init__(self, pin, nedges, tblock, callback, *args): # Optional args for callback
44 self._nedges = nedges
45 self._tblock = tblock
46 self.callback = callback
47 self.args = args
48
49 self._times = array('i', (0 for _ in range(nedges + 1))) # +1 for overrun
50 if platform == 'pyboard':
51 ExtInt(pin, ExtInt.IRQ_RISING_FALLING, Pin.PULL_NONE, self._cb_pin)
52 elif ESP32:
53 pin.irq(handler = self._cb_pin, trigger = (Pin.IRQ_FALLING | Pin.IRQ_RISING))
54 else:
55 pin.irq(handler = self._cb_pin, trigger = (Pin.IRQ_FALLING | Pin.IRQ_RISING), hard = True)
56 self.edge = 0
57 self.tim = Timer(-1) # Sofware timer
58 self.cb = self._decode
59
60
61 # Pin interrupt. Save time of each edge for later decode.
62 def _cb_pin(self, line):
63 t = ticks_us()
64 # On overrun ignore pulses until software timer times out
65 if self.edge <= self._nedges: # Allow 1 extra pulse to record overrun
66 if not self.edge: # First edge received
67 self.tim.init(period=self._tblock , mode=Timer.ONE_SHOT, callback=self.cb)
68 self._times[self.edge] = t
69 self.edge += 1
70
71 class NEC_IR(IR_RX):
72 def __init__(self, pin, callback, extended, *args):
73 # Block lasts <= 80ms and has 68 edges
74 tblock = 80 if extended else 73 # Allow for some tx tolerance (?)
75 super().__init__(pin, 68, tblock, callback, *args)
76 self._extended = extended
77 self._addr = 0
78
79 def _decode(self, _):
80 overrun = self.edge > 68
81 val = OVERRUN if overrun else BADSTART
82 if not overrun:
83 width = ticks_diff(self._times[1], self._times[0])
84 if width > 4000: # 9ms leading mark for all valid data
85 width = ticks_diff(self._times[2], self._times[1])
86 if width > 3000: # 4.5ms space for normal data
87 if self.edge < 68:
88 # Haven't received the correct number of edges
89 val = BADBLOCK
90 else:
91 # Time spaces only (marks are always 562.5µs)
92 # Space is 1.6875ms (1) or 562.5µs (0)
93 # Skip last bit which is always 1
94 val = 0
95 for edge in range(3, 68 - 2, 2):
96 val >>= 1
97 if ticks_diff(self._times[edge + 1], self._times[edge]) > 1120:
98 val |= 0x80000000
99 elif width > 1700: # 2.5ms space for a repeat code. Should have exactly 4 edges.
100 val = REPEAT if self.edge == 4 else BADREP
101 addr = 0
102 if val >= 0: # validate. Byte layout of val ~cmd cmd ~addr addr
103 addr = val & 0xff
104 cmd = (val >> 16) & 0xff
105 if addr == ((val >> 8) ^ 0xff) & 0xff: # 8 bit address OK
106 val = cmd if cmd == (val >> 24) ^ 0xff else BADDATA
107 self._addr = addr
108 else:
109 addr |= val & 0xff00 # pass assumed 16 bit address to callback
110 if self._extended:
111 val = cmd if cmd == (val >> 24) ^ 0xff else BADDATA
112 self._addr = addr
113 else:
114 val = BADADDR
115 if val == REPEAT:
116 addr = self._addr # Last valid addresss
117 self.edge = 0 # Set up for new data burst and run user callback
118 self.callback(val, addr, *self.args)
119
120 class RC5_IR(IR_RX):
121 def __init__(self, pin, callback, *args):
122 # Block lasts <= 30ms and has <= 28 edges
123 super().__init__(pin, 28, 30, callback, *args)
124
125 def _decode(self, _):
126 try:
127 nedges = self.edge # No. of edges detected
128 if not 14 <= nedges <= 28:
129 raise RuntimeError(OVERRUN if nedges > 28 else BADSTART)
130 # Regenerate bitstream
131 bits = 0
132 bit = 1
133 for x in range(1, nedges):
134 width = ticks_diff(self._times[x], self._times[x - 1])
135 if not 500 < width < 2000:
136 raise RuntimeError(BADBLOCK)
137 for _ in range(1 if width < 1334 else 2):
138 bits <<= 1
139 bits |= bit
140 bit ^= 1
141 #print(bin(bits)) # Matches inverted scope waveform
142 # Decode Manchester code
143 x = 30
144 while not bits >> x:
145 x -= 1
146 m0 = 1 << x # Mask MS two bits (always 01)
147 m1 = m0 << 1
148 v = 0 # 14 bit bitstream
149 for _ in range(14):
150 v <<= 1
151 b0 = (bits & m0) > 0
152 b1 = (bits & m1) > 0
153 if b0 == b1:
154 raise RuntimeError(BADBLOCK)
155 v |= b0
156 m0 >>= 2
157 m1 >>= 2
158 # Split into fields (val, addr, ctrl)
159 val = (v & 0x3f) | (0x40 if ((v >> 12) & 1) else 0)
160 addr = (v >> 6) & 0x1f
161 ctrl = (v >> 11) & 1
162
163 except RuntimeError as e:
164 val, addr, ctrl = e.args[0], 0, 0
165 self.edge = 0 # Set up for new data burst and run user callback
166 self.callback(val, addr, ctrl, *self.args)
167
168 class RC6_M0(IR_RX):
169 # Even on Pyboard D the 444μs nominal pulses can be recorded as up to 705μs
170 # Scope shows 360-520 μs (-84μs +76μs relative to nominal)
171 # Header nominal 2666, 889, 444, 889, 444, 444, 444, 444 carrier ON at end
172 hdr = ((1800, 4000), (593, 1333), (222, 750), (593, 1333), (222, 750), (222, 750), (222, 750), (222, 750))
173 def __init__(self, pin, callback, *args):
174 # Block lasts 23ms nominal and has <=44 edges
175 super().__init__(pin, 44, 30, callback, *args)
176
177 def _decode(self, _):
178 try:
179 nedges = self.edge # No. of edges detected
180 if not 22 <= nedges <= 44:
181 raise RuntimeError(OVERRUN if nedges > 28 else BADSTART)
182 for x, lims in enumerate(self.hdr):
183 width = ticks_diff(self._times[x + 1], self._times[x])
184 #print('x = {}, width = {}, lims = {}'.format(x, width, lims))
185 if not (lims[0] < width < lims[1]):
186 #print('Bad start', x, width, lims)
187 raise RuntimeError(BADSTART)
188 x += 1
189 width = ticks_diff(self._times[x + 1], self._times[x])
190 # Long bit is 889μs (0) or 1333μs (1)
191 ctrl = width > 1111 # If 1333, ctrl == True and carrier is off
192 start = x + 2 if ctrl else x + 3 # Skip 2nd long bit
193
194 # Regenerate bitstream
195 bits = 1 # MSB is a dummy 1 to mark start of bitstream
196 bit = int(ctrl)
197 for x in range(start, nedges - 1):
198 width = ticks_diff(self._times[x + 1], self._times[x])
199 if not 222 < width < 1333:
200 #print('Width', width, 'x', x)
201 raise RuntimeError(BADBLOCK)
202 for _ in range(1 if width < 666 else 2):
203 bits <<= 1
204 bits |= bit
205 bit ^= 1
206 print('36-bit format {:036b} x={} nedges={}'.format(bits, x, nedges))
207
208 # Decode Manchester code. Bitstream varies in length: find MS 1.
209 x = 36
210 while not bits >> x:
211 x -= 1
212 # Now points to dummy 1
213 x -= 2 # Point to MS biphase pair
214 m0 = 1 << x
215 m1 = m0 << 1 # MSB of pair
216 v = 0 # 16 bit bitstream
217 for _ in range(16):
218 v <<= 1
219 b0 = (bits & m0) > 0
220 b1 = (bits & m1) > 0
221 print(int(b1), int(b0))
222 if b0 == b1:
223 raise RuntimeError(BADBLOCK)
224 v |= b1
225 m0 >>= 2
226 m1 >>= 2
227 # Split into fields (val, addr)
228 val = v & 0xff
229 addr = (v >> 8) & 0xff
230
231 except RuntimeError as e:
232 val, addr, ctrl = e.args[0], 0, 0
233 self.edge = 0 # Set up for new data burst and run user callback
234 self.callback(val, addr, ctrl, *self.args)