]> vault307.fbx.one Git - micorpython_ir.git/blob - ir_tx/__init__.py
Fix issue 14 NEC transmit error on RP2.
[micorpython_ir.git] / ir_tx / __init__.py
1 # __init__.py Nonblocking IR blaster
2 # Runs on Pyboard D or Pyboard 1.x (not Pyboard Lite), ESP32 and RP2
3
4 # Released under the MIT License (MIT). See LICENSE.
5
6 # Copyright (c) 2020-2021 Peter Hinch
7 from sys import platform
8 ESP32 = platform == 'esp32' # Loboris not supported owing to RMT
9 RP2 = platform == 'rp2'
10 if ESP32:
11 from machine import Pin, PWM
12 from esp32 import RMT
13 elif RP2:
14 from .rp2_rmt import RP2_RMT
15 else:
16 from pyb import Pin, Timer # Pyboard does not support machine.PWM
17
18 from micropython import const
19 from array import array
20 from time import ticks_us, ticks_diff
21 # import micropython
22 # micropython.alloc_emergency_exception_buf(100)
23
24
25 # Shared by NEC
26 STOP = const(0) # End of data
27
28 # IR abstract base class. Array holds periods in μs between toggling 36/38KHz
29 # carrier on or off. Physical transmission occurs in an ISR context controlled
30 # by timer 2 and timer 5. See TRANSMITTER.md for details of operation.
31 class IR:
32 _active_high = True # Hardware turns IRLED on if pin goes high.
33 _space = 0 # Duty ratio that causes IRLED to be off
34 timeit = False # Print timing info
35
36 @classmethod
37 def active_low(cls):
38 if ESP32:
39 raise ValueError('Cannot set active low on ESP32')
40 cls._active_high = False
41 cls._space = 100
42
43 def __init__(self, pin, cfreq, asize, duty, verbose):
44 if ESP32:
45 self._rmt = RMT(0, pin=pin, clock_div=80, tx_carrier = (cfreq, duty, 1))
46 # 1μs resolution
47 elif RP2: # PIO-based RMT-like device
48 self._rmt = RP2_RMT(pin_pulse=None, carrier=(pin, cfreq, duty)) # 1μs resolution
49 asize += 1 # Allow for possible extra space pulse
50 else: # Pyboard
51 if not IR._active_high:
52 duty = 100 - duty
53 tim = Timer(2, freq=cfreq) # Timer 2/pin produces 36/38/40KHz carrier
54 self._ch = tim.channel(1, Timer.PWM, pin=pin)
55 self._ch.pulse_width_percent(self._space) # Turn off IR LED
56 # Pyboard: 0 <= pulse_width_percent <= 100
57 self._duty = duty
58 self._tim = Timer(5) # Timer 5 controls carrier on/off times
59 self._tcb = self._cb # Pre-allocate
60 self._arr = array('H', 0 for _ in range(asize)) # on/off times (μs)
61 self._mva = memoryview(self._arr)
62 # Subclass interface
63 self.verbose = verbose
64 self.carrier = False # Notional carrier state while encoding biphase
65 self.aptr = 0 # Index into array
66
67 def _cb(self, t): # T5 callback, generate a carrier mark or space
68 t.deinit()
69 p = self.aptr
70 v = self._arr[p]
71 if v == STOP:
72 self._ch.pulse_width_percent(self._space) # Turn off IR LED.
73 return
74 self._ch.pulse_width_percent(self._space if p & 1 else self._duty)
75 self._tim.init(prescaler=84, period=v, callback=self._tcb)
76 self.aptr += 1
77
78 # Public interface
79 # Before populating array, zero pointer, set notional carrier state (off).
80 def transmit(self, addr, data, toggle=0, validate=False): # NEC: toggle is unused
81 t = ticks_us()
82 if validate:
83 if addr > self.valid[0] or addr < 0:
84 raise ValueError('Address out of range', addr)
85 if data > self.valid[1] or data < 0:
86 raise ValueError('Data out of range', data)
87 if toggle > self.valid[2] or toggle < 0:
88 raise ValueError('Toggle out of range', toggle)
89 self.aptr = 0 # Inital conditions for tx: index into array
90 self.carrier = False
91 self.tx(addr, data, toggle) # Subclass populates ._arr
92 self.trigger() # Initiate transmission
93 if self.timeit:
94 dt = ticks_diff(ticks_us(), t)
95 print('Time = {}μs'.format(dt))
96
97 # Subclass interface
98 def trigger(self): # Used by NEC to initiate a repeat frame
99 if ESP32:
100 self._rmt.write_pulses(tuple(self._mva[0 : self.aptr]))
101 elif RP2:
102 self.append(STOP)
103 self._rmt.send(self._arr)
104 else:
105 self.append(STOP)
106 self.aptr = 0 # Reset pointer
107 self._cb(self._tim) # Initiate physical transmission.
108
109 def append(self, *times): # Append one or more time peiods to ._arr
110 for t in times:
111 self._arr[self.aptr] = t
112 self.aptr += 1
113 self.carrier = not self.carrier # Keep track of carrier state
114 self.verbose and print('append', t, 'carrier', self.carrier)
115
116 def add(self, t): # Increase last time value (for biphase)
117 assert t > 0
118 self.verbose and print('add', t)
119 # .carrier unaffected
120 self._arr[self.aptr - 1] += t
121
122
123 # Given an iterable (e.g. list or tuple) of times, emit it as an IR stream.
124 class Player(IR):
125
126 def __init__(self, pin, freq=38000, verbose=False): # NEC specifies 38KHz
127 super().__init__(pin, freq, 68, 33, verbose) # Measured duty ratio 33%
128
129 def play(self, lst):
130 for x, t in enumerate(lst):
131 self._arr[x] = t
132 self.aptr = x + 1
133 self.trigger()