# __init__.py Nonblocking IR blaster
-# Runs on Pyboard D or Pyboard 1.x (not Pyboard Lite) and ESP32
+# 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 Peter Hinch
+# 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
-import micropython
-
+from time import ticks_us, ticks_diff
+# import micropython
# micropython.alloc_emergency_exception_buf(100)
-# ABC and Pyboard only: ESP32 ignores this value.
-# Duty ratio in carrier off state: if driver is such that 3.3V turns the LED
-# off, set _SPACE = 100
-_SPACE = const(0)
-# On ESP32 gate hardware design is led_on = rmt and carrier
# 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 README.md for details of operation.
+# 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._pwm = PWM(pin[0]) # Continuous 36/38/40KHz carrier
- self._pwm.deinit()
- # ESP32: 0 <= duty <= 1023
- self._pwm.init(freq=cfreq, duty=round(duty * 10.23))
- self._rmt = RMT(0, pin=pin[1], clock_div=80) # 1μs resolution
+ 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
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(_SPACE) # Turn off IR LED
+ self._ch.pulse_width_percent(self._space) # Turn off IR LED
# Pyboard: 0 <= pulse_width_percent <= 100
- self._duty = duty if not _SPACE else (100 - duty)
+ 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)
p = self.aptr
v = self._arr[p]
if v == STOP:
- self._ch.pulse_width_percent(_SPACE) # Turn off IR LED.
+ self._ch.pulse_width_percent(self._space) # Turn off IR LED.
return
- self._ch.pulse_width_percent(_SPACE if p & 1 else self._duty)
+ 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): # NEC: toggle is unused
+ 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]), start = 1)
+ 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.verbose and print('add', t)
# .carrier unaffected
self._arr[self.aptr - 1] += t
+
+
+# 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()