]> vault307.fbx.one Git - micorpython_ir.git/blob - ir_tx.py
Prior to portable tx branch
[micorpython_ir.git] / ir_tx.py
1 # ir_tx.py Nonblocking IR blaster
2 # Runs on Pyboard D or Pyboard 1.x only (not Pyboard Lite)
3
4 # Released under the MIT License (MIT). See LICENSE.
5
6 # Copyright (c) 2020 Peter Hinch
7
8 from pyb import Pin, Timer
9 from micropython import const
10 from array import array
11 import micropython
12
13 # micropython.alloc_emergency_exception_buf(100)
14
15 # Common
16 _SPACE = const(0) # Or 100. Depends on wiring: 0 assumes logic 0 turns IR off.
17 _STOP = const(0) # End of data
18 # NEC
19 _TBURST = const(563)
20 _T_ONE = const(1687)
21 # RC5
22 _T_RC5 = const(889) # Time for pulse of carrier
23 # RC6_M0
24 _T_RC6 = const(444)
25 _T2_RC6 = const(889)
26
27 # IR abstract base class. Array holds periods in μs between toggling 36/38KHz
28 # carrier on or off. Physical transmission occurs in an ISR context controlled
29 # by timer 2 and timer 5. See README.md for details of operation.
30 class IR:
31
32 def __init__(self, pin, freq, asize, duty, verbose):
33 tim = Timer(2, freq=freq) # Timer 2/pin produces 36/38KHz carrier
34 self._ch = tim.channel(1, Timer.PWM, pin=pin)
35 self._ch.pulse_width_percent(_SPACE) # Turn off IR LED
36 self._duty = duty if not _SPACE else (100 - duty)
37 self._tim = Timer(5) # Timer 5 controls carrier on/off times
38 self._tcb = self.cb # Pre-allocate
39 self.verbose = verbose
40 self.arr = array('H', 0 for _ in range(asize)) # on/off times (μs)
41 self.carrier = False # Notional carrier state while encoding biphase
42 self.aptr = 0 # Index into array
43
44 # Before populating array, zero pointer, set notional carrier state (off).
45 def transmit(self, addr, data, toggle=0): # NEC: toggle is unused
46 self.aptr = 0 # Inital conditions for tx: index into array
47 self.carrier = False
48 self.tx(addr, data, toggle)
49 self.append(_STOP)
50 self.aptr = 0 # Reset pointer
51 self.cb(self._tim) # Initiate physical transmission.
52
53 def cb(self, t): # T5 callback, generate a carrier mark or space
54 t.deinit()
55 p = self.aptr
56 v = self.arr[p]
57 if v == _STOP:
58 self._ch.pulse_width_percent(_SPACE) # Turn off IR LED.
59 return
60 self._ch.pulse_width_percent(_SPACE if p & 1 else self._duty)
61 self._tim.init(prescaler=84, period=v, callback=self._tcb)
62 self.aptr += 1
63
64 def append(self, *times): # Append one or more time peiods to .arr
65 for t in times:
66 self.arr[self.aptr] = t
67 self.aptr += 1
68 self.carrier = not self.carrier # Keep track of carrier state
69 self.verbose and print('append', t, 'carrier', self.carrier)
70
71 def add(self, t): # Increase last time value
72 self.verbose and print('add', t)
73 self.arr[self.aptr - 1] += t # Carrier unaffected
74
75 # NEC protocol
76 class NEC(IR):
77
78 def __init__(self, pin, freq=38000, verbose=False): # NEC specifies 38KHz
79 super().__init__(pin, freq, 68, 50, verbose)
80
81 def _bit(self, b):
82 self.append(_TBURST, _T_ONE if b else _TBURST)
83
84 def tx(self, addr, data, _): # Ignore toggle
85 self.append(9000, 4500)
86 if addr < 256: # Short address: append complement
87 addr |= ((addr ^ 0xff) << 8)
88 for _ in range(16):
89 self._bit(addr & 1)
90 addr >>= 1
91 data |= ((data ^ 0xff) << 8)
92 for _ in range(16):
93 self._bit(data & 1)
94 data >>= 1
95 self.append(_TBURST)
96
97 def repeat(self):
98 self.aptr = 0
99 self.append(9000, 2250, _TBURST, _STOP)
100 self.aptr = 0 # Reset pointer
101 self.cb(self._tim) # Initiate physical transmission.
102
103 # NEC protocol
104 class SONY(IR):
105
106 def __init__(self, pin, bits=12, freq=40000, verbose=False): # Sony specifies 40KHz
107 super().__init__(pin, freq, 3 + bits * 2, 30, verbose)
108 if bits not in (12, 15, 20):
109 raise ValueError('bits must be 12, 15 or 20.')
110 self.bits = bits
111
112 def tx(self, addr, data, ext):
113 self.append(2400, 600)
114 bits = self.bits
115 v = data & 0x7f
116 if bits == 12:
117 v |= (addr & 0x1f) << 7
118 elif bits == 15:
119 v |= (addr & 0xff) << 7
120 else:
121 v |= (addr & 0x1f) << 7
122 v |= (ext & 0xff) << 12
123 for _ in range(bits):
124 self.append(1200 if v & 1 else 600, 600)
125 v >>= 1
126
127
128 # Philips RC5 protocol
129 class RC5(IR):
130
131 def __init__(self, pin, freq=36000, verbose=False):
132 super().__init__(pin, freq, 28, 30, verbose)
133
134 def tx(self, addr, data, toggle):
135 d = (data & 0x3f) | ((addr & 0x1f) << 6) | ((data & 0x40) << 6) | ((toggle & 1) << 11)
136 self.verbose and print(bin(d))
137 mask = 0x2000
138 while mask:
139 if mask == 0x2000:
140 self.append(_T_RC5)
141 else:
142 bit = bool(d & mask)
143 if bit ^ self.carrier:
144 self.add(_T_RC5)
145 self.append(_T_RC5)
146 else:
147 self.append(_T_RC5, _T_RC5)
148 mask >>= 1
149
150 # Philips RC6 mode 0 protocol
151 class RC6_M0(IR):
152
153 def __init__(self, pin, freq=36000, verbose=False):
154 super().__init__(pin, freq, 44, 30, verbose)
155
156 def tx(self, addr, data, toggle):
157 # leader, 1, 0, 0, 0
158 self.append(2666, _T2_RC6, _T_RC6, _T2_RC6, _T_RC6, _T_RC6, _T_RC6, _T_RC6, _T_RC6)
159 # Append a single bit of twice duration
160 if toggle:
161 self.add(_T2_RC6)
162 self.append(_T2_RC6)
163 else:
164 self.append(_T2_RC6, _T2_RC6)
165 d = (data & 0xff) | ((addr & 0xff) << 8)
166 mask = 0x8000
167 self.verbose and print('toggle', toggle, self.carrier, bool(d & mask))
168 while mask:
169 bit = bool(d & mask)
170 if bit ^ self.carrier:
171 self.append(_T_RC6, _T_RC6)
172 else:
173 self.add(_T_RC6)
174 self.append(_T_RC6)
175 mask >>= 1