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