# Copyright (c) 2020 Peter Hinch
from sys import platform
-ESP32 = platform == 'esp32' or platform == 'esp32_LoBo'
+ESP32 = platform == 'esp32' # Loboris not supported owing to RMT
if ESP32:
- from machine import Pin, Timer, PWM, freq
+ from machine import Pin, PWM
+ from esp32 import RMT
else:
from pyb import Pin, Timer # Pyboard does not support machine.PWM
# micropython.alloc_emergency_exception_buf(100)
-# ABC only
+# 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)
-# If the wiring is such that 3.3V turns the LED off, set _SPACE as follows
-# On Pyboard 100, on ESP32 1023
+# On ESP32 gate hardware design is led_on = rmt and carrier
+
# Shared by NEC
STOP = const(0) # End of data
def __init__(self, pin, cfreq, asize, duty, verbose):
if ESP32:
- freq(240000000)
- self._pwm = PWM(pin) # Produces 36/38/40KHz carrier
+ self._pwm = PWM(pin[0]) # Continuous 36/38/40KHz carrier
self._pwm.deinit()
- self._pwm.init(freq=cfreq, duty=_SPACE)
# ESP32: 0 <= duty <= 1023
- self._duty = round((duty if not _SPACE else (100 - duty)) * 10.23)
- self._tim = Timer(-1) # Controls carrier on/off times
- self._off = self.esp_off # Turn IR LED off
- self._onoff = self.esp_onoff # Set IR LED state and refresh timer
+ self._pwm.init(freq=cfreq, duty=round(duty * 10.23))
+ self._rmt = RMT(0, pin=pin[1], clock_div=80) # 1μs resolution
else: # Pyboard
tim = Timer(2, freq=cfreq) # Timer 2/pin produces 36/38/40KHz carrier
self._ch = tim.channel(1, Timer.PWM, pin=pin)
# Pyboard: 0 <= pulse_width_percent <= 100
self._duty = duty if not _SPACE else (100 - duty)
self._tim = Timer(5) # Timer 5 controls carrier on/off times
- self._off = self.pb_off
- self._onoff = self.pb_onoff
- self._tcb = self.cb # Pre-allocate
+ 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.arr = array('H', 0 for _ in range(asize)) # on/off times (μs)
self.carrier = False # Notional carrier state while encoding biphase
self.aptr = 0 # Index into array
- # Before populating array, zero pointer, set notional carrier state (off).
- def transmit(self, addr, data, toggle=0): # NEC: toggle is unused
- self.aptr = 0 # Inital conditions for tx: index into array
- self.carrier = False
- self.tx(addr, data, toggle)
- self.append(STOP)
- self.aptr = 0 # Reset pointer
- self.cb(self._tim) # Initiate physical transmission.
-
- # Turn IR LED off (pyboard and ESP32 variants)
- def pb_off(self):
- self._ch.pulse_width_percent(_SPACE)
-
- def esp_off(self):
- self._pwm.duty(_SPACE)
-
- # Turn IR LED on or off and re-initialise timer (pyboard and ESP32 variants)
- @micropython.native
- def pb_onoff(self, p, v):
- self._ch.pulse_width_percent(_SPACE if p & 1 else self._duty)
- self._tim.init(prescaler=84, period=v, callback=self._tcb)
-
- @micropython.native
- def esp_onoff(self, p, v):
- self._pwm.duty(_SPACE if p & 1 else self._duty)
- self._tim.init(mode=Timer.ONE_SHOT, freq=v, callback=self.cb)
-
- def cb(self, t): # T5 callback, generate a carrier mark or space
+ def _cb(self, t): # T5 callback, generate a carrier mark or space
t.deinit()
p = self.aptr
- v = self.arr[p]
+ v = self._arr[p]
if v == STOP:
- self._off() # Turn off IR LED.
+ self._ch.pulse_width_percent(_SPACE) # Turn off IR LED.
return
- self._onoff(p, v)
+ self._ch.pulse_width_percent(_SPACE if p & 1 else self._duty)
+ self._tim.init(prescaler=84, period=v, callback=self._tcb)
self.aptr += 1
- def append(self, *times): # Append one or more time peiods to .arr
+ # Public interface
+ # Before populating array, zero pointer, set notional carrier state (off).
+ def transmit(self, addr, data, toggle=0): # NEC: toggle is unused
+ 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
+
+ # 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)
+ 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:
- if ESP32 and t:
- t -= 350 # ESP32 sluggishness
- t = round(1_000_000 / t) # Store in Hz
- self.arr[self.aptr] = t
+ 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
+ def add(self, t): # Increase last time value (for biphase)
assert t > 0
self.verbose and print('add', t)
# .carrier unaffected
- if ESP32:
- t -= 350
- self.arr[self.aptr - 1] = round((self.arr[self.aptr - 1] / 1_000_000 + t) / 1_000_000)
- else:
- self.arr[self.aptr - 1] += t
+ self._arr[self.aptr - 1] += t