X-Git-Url: https://vault307.fbx.one/gitweb/micorpython_ir.git/blobdiff_plain/36140aa568d4a538adafb03e575b26de8a0784ef..1a533f01992659b56d0fa0d52422b1b796897516:/ir_tx/__init__.py diff --git a/ir_tx/__init__.py b/ir_tx/__init__.py index ef52e84..ac9755c 100644 --- a/ir_tx/__init__.py +++ b/ir_tx/__init__.py @@ -1,113 +1,132 @@ # __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' or platform == 'esp32_LoBo' +ESP32 = platform == 'esp32' # Loboris not supported owing to RMT +RP2 = platform == 'rp2' if ESP32: - from machine import Pin, Timer, PWM, freq + 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 only -_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 + # 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: - freq(240000000) - self._pwm = PWM(pin) # Produces 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._rmt = RMT(0, pin=pin, clock_div=80, carrier_freq=cfreq, + carrier_duty_percent=duty) # 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._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(self._space) # Turn off IR LED. return - self._onoff(p, v) + 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 - 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, 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) + 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: - 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 + + +# 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()