]> vault307.fbx.one Git - micorpython_ir.git/commitdiff
Add MCE. Fix bug with RC5X.
authorPeter Hinch <peter@hinch.me.uk>
Fri, 20 Mar 2020 17:24:05 +0000 (17:24 +0000)
committerPeter Hinch <peter@hinch.me.uk>
Fri, 20 Mar 2020 17:24:05 +0000 (17:24 +0000)
13 files changed:
README.md
RECEIVER.md
TRANSMITTER.md
ir_rx/acquire.py
ir_rx/mce.py [new file with mode: 0644]
ir_rx/philips.py
ir_rx/test.py
ir_tx/__init__.py
ir_tx/mce.py [new file with mode: 0644]
ir_tx/mcetest.py [new file with mode: 0644]
ir_tx/nec.py
ir_tx/philips.py
ir_tx/test.py

index 44318d546476d17cccac2cbe812d0139d497aead..2eb3a47f35bbe7760bbc3cb6288f4ea8538efd5e 100644 (file)
--- a/README.md
+++ b/README.md
@@ -21,12 +21,23 @@ IR communication uses a carrier frequency to pulse the IR source. Modulation
 takes the form of OOK (on-off keying). There are multiple protocols and at
 least three options for carrier frequency: 36, 38 and 40KHz.
 
+In the case of the transmitter the carrier frequency is a runtime parameter:
+any value may be specified. The receiver uses a hardware demodulator which
+should be purchased for the correct frequency. The receiver device driver sees
+the demodulated signal and is hence carrier frequency agnostic.
+
+Remotes transmit an address and a data byte, plus in some cases an extra value.
+The address denotes the physical device being controlled. The data defines the
+button on the remote. Provision usually exists for differentiating between a
+button repeatedly pressed and one which is held down; the mechanism is protocol
+dependent.
+
+# 2. Supported protocols
+
 The drivers support NEC and Sony protocols plus two Philips protocols, namely
-RC-5 and RC-6 mode 0. In the case of the transmitter the carrier frequency is a
-runtime parameter: any value may be specified. The receiver uses a hardware
-demodulator which should be purchased for the correct frequency. The receiver
-device driver sees the demodulated signal and is hence carrier frequency
-agnostic.
+RC-5 and RC-6 mode 0. There is also support for the OrtekMCE protocol used on
+VRC-1100 remotes. These originally supported Microsoft Media Center but can be
+used to control Kodi and (with a suitable receiver) to emulate a PC keyboard.
 
 Examining waveforms from various remote controls it is evident that numerous
 protocols exist. Some are doubtless proprietary and undocumented. The supported
@@ -37,13 +48,7 @@ timing.
 
 A remote using the NEC protocol is [this one](https://www.adafruit.com/products/389).
 
-Remotes transmit an address and a data byte, plus in some cases an extra value.
-The address denotes the physical device being controlled. The data defines the
-button on the remote. Provision usually exists for differentiating between a
-button repeatedly pressed and one which is held down; the mechanism is protocol
-dependent.
-
-# 2. Hardware Requirements
+# 3. Hardware Requirements
 
 These are discussed in detail in the relevant docs; the following provides an
 overview.
@@ -60,5 +65,41 @@ is obviously neither guaranteed nor optimal.
 
 The transmitter requires a Pyboard 1.x (not Lite), a Pyboard D or an ESP32.
 Output is via an IR LED which will need a transistor to provide sufficient
-current. The ESP32 has significant limitations as a transmitter discussed
-[here](./TRANSMITTER.md#52-esp32).
+current. The ESP32 requires an extra transistor to work as a transmitter.
+
+## 3.1 Carrier frequencies
+
+These are as follows. The Samsung and Panasonic remotes appear to use
+proprietary protocols and are not supported by these drivers.
+
+| Protocol  | F KHz | How found     | Support |
+|:---------:|:-----:|:-------------:|:-------:|
+| NEC       | 38    | Measured      | Y       |
+| RC-5 RC-6 | 36    | Spec/measured | Y       |
+| Sony      | 40    | Spec/measured | Y       |
+| MCE       | 38    | Measured      | Y       | 
+| Samsung   | 38    | Measured      | N       |
+| Panasonic | 36.3  | Measured      | N       |
+
+# 4. References
+
+Sources of information about IR protocols.  
+[General information about IR](https://www.sbprojects.net/knowledge/ir/)
+
+The NEC protocol:  
+[altium](http://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol)  
+[circuitvalley](http://www.circuitvalley.com/2013/09/nec-protocol-ir-infrared-remote-control.html)
+
+Philips protocols:  
+[RC5](https://en.wikipedia.org/wiki/RC-5)  
+[RC5](https://www.sbprojects.net/knowledge/ir/rc5.php)  
+[RC6](https://www.sbprojects.net/knowledge/ir/rc6.php)
+
+Sony protocol:  
+[SIRC](https://www.sbprojects.net/knowledge/ir/sirc.php)
+
+MCE protocol:  
+[OrtekMCE](http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE)
+
+IR decoders (C sourcecode):  
+[in the Linux kernel](https://github.com/torvalds/linux/tree/master/drivers/media/rc)
index 12d9577ad02cb1a59ff4e5fb356e4adfba2f6a76..ac20e18053898572badb6fe7eae1d0865ef06767 100644 (file)
@@ -52,11 +52,10 @@ the above script or run the following:
 from ir_rx.acquire import test
 test()
 ```
-This script is under development.
-
-It waits for a single burst from the remote and prints the timing of the pulses
-followed by its best guess at the protocol. It correctly identifies supported
-protocols, but can wrongly identify some unsupported proprietary protocols.
+This script waits for a single burst from the remote and prints the timing of
+the pulses followed by its best guess at the protocol. It correctly identifies
+supported protocols, but can wrongly identify unsupported protocols. The
+behaviour of the script exposed to an unknown protocol is unpredictable.
 
 # 3. The driver
 
@@ -172,7 +171,40 @@ Typical invocation:
 from ir_rx.philips import RC5_IR
 ```
 
-These support the RC-5 and RC-6 mode 0 protocols respectively.
+These support the RC-5 (including RC-5X) and RC-6 mode 0 protocols
+respectively.
+
+#### Microsoft MCE class
+
+`MCE`
+
+Typical invocation:
+```python
+from ir_rx.mce import MCE
+```
+
+I have been unable to locate a definitive specification: the protocol was
+analysed by a mixture of googling and experiment. Behaviour may change if I
+acquire new information. The protocol is known as OrtekMCE and the remote
+control is sold on eBay as VRC-1100.
+
+The remote was designed for Microsoft Media Center and is used to control Kodi
+on boxes such as the Raspberry Pi. With a suitable PC driver it can emulate a
+PC keyboard and mouse. The mouse emulation uses a different protocol: the class
+does not currently support it. Pressing mouse buttons and pad will cause the
+error function (if provided) to be called.
+
+Args passed to the callback comprise 4 bit `addr`, 6 bit `data` and 2 bit `ctrl`
+with the latter having the value 0 for the first message and 2 for the message
+sent on key release. Intermediate messages (where the key is held down) have
+value 1.
+
+There is a 4-bit checksum which is used by default. The algorithm requires an
+initial 'seed' value which my testing proved to be 4. However the only
+[documentation](http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE) I
+could find stated that the value should be 3. I implemented this as a class
+variable `MCE.init_cs=4`. This enables it to be changed if some remotes use 3.
+If the value is set to -1 the check will be skipped.
 
 # 4. Errors
 
@@ -248,22 +280,6 @@ CPU times used by `.decode` (not including the user callback) were measured on
 a Pyboard D SF2W at stock frequency. They were: NEC 1ms for normal data, 100μs
 for a repeat code. Philips codes: RC-5 900μs, RC-6 mode 0 5.5ms.
 
-# 7. References
-
-[General information about IR](https://www.sbprojects.net/knowledge/ir/)
-
-The NEC protocol:  
-[altium](http://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol)  
-[circuitvalley](http://www.circuitvalley.com/2013/09/nec-protocol-ir-infrared-remote-control.html)
-
-Philips protocols:  
-[RC5](https://en.wikipedia.org/wiki/RC-5)  
-[RC5](https://www.sbprojects.net/knowledge/ir/rc5.php)  
-[RC6](https://www.sbprojects.net/knowledge/ir/rc6.php)
-
-Sony protocol:  
-[SIRC](https://www.sbprojects.net/knowledge/ir/sirc.php)
-
 # Appendix 1 NEC Protocol description
 
 A normal burst comprises exactly 68 edges, the exception being a repeat code
@@ -279,3 +295,18 @@ provide error checking. This also ensures that the number of 1's and 0's in a
 burst is constant, giving a constant burst length of 67.5ms. In extended
 address mode this constancy is lost. The burst length can (by my calculations)
 run to 76.5ms.
+
+# Appendix 2 MCE Protocol
+
+The bitstream comprises a header (2ms mark, 1ms space) followed by 16 bits of
+Manchester encoded data with a bit time of 500μs. Data are encoded  
+```
+ccccddddddppaaaa
+```
+Where `aaaa` is the address, `pp` is the position (toggle) field, `dddddd` is
+data and `cccc` is a checksum. This is calculated by counting the ones in
+`ddddddppaaaa` and adding 4. Data are transmitted LSB first.
+
+The only [doc](http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE) I
+could find states that the checksum seed value is 3, but this did not match the
+remote I have.
index c29a0ddbd840f0a9c924cbe500ee7db3e3365629..4f15c656e190e56494ff0a6f3f76593f7a3bf445 100644 (file)
@@ -31,7 +31,7 @@ The transistor type is not critical.
 
 The driver assumes circuits as shown. Here the carrier "off" state is 0V,
 which is the driver default. If using a circuit where "off" is required to be
-3.3V, the constant `_SPACE` in `ir_tx.__init__.py` should be changed to 100.
+3.3V, the class variable `active_high` should be set `False`.
 
 ## 1.2 ESP32 Wiring
 
@@ -106,8 +106,18 @@ occurs as a background process, on the Pyboard controlled by timers 2 and 5.
 Execution times on a Pyboard 1.1 were 3.3ms for NEC, 1.5ms for RC5 and 2ms
 for RC6.
 
+Class variable:
+ 1. `active_high=True` Normally the IR LED drive circuit turns the LED on if
+ the pin goes high. If it works with the opposite polarity the variable should
+ be set `False` before instantiating.
+
 #### NEC class
 
+Class `NEC`. Example invocation:
+```python
+from ir_tx.nec import NEC
+```
+
 This has an additional method `.repeat` (no args). This causes a repeat code to
 be transmitted. Should be called every 108ms if a button is held down.
 
@@ -121,6 +131,11 @@ A value passed in `toggle` is ignored.
 
 #### Sony classes
 
+Classes `SONY_12`, `SONY_15` and `SONY_20`. Example invocation:
+```python
+from ir_tx.sony import SONY_15
+```
+
 The SIRC protocol supports three sizes, supported by the following classes:
  1. 12 bit (7 data, 5 address) `SONY_12`
  2. 15 bit (7 data, 8 address) `SONY_15`
@@ -132,6 +147,11 @@ value.
 
 #### Philips classes
 
+Classes `RC5` and `RC6_M0`. Example invocation:
+```python
+from ir_tx.philips import RC5
+```
+
 The RC-5 protocol supports a 5 bit address and 6 or 7 bit (RC5X) data. The
 driver uses the appropriate mode depending on the `data` value provided.
 
@@ -141,6 +161,42 @@ Both send a `toggle` bit which remains constant if a button is held down, but
 changes when the button is released. The application should implement this
 behaviour, setting the `toggle` arg of `.transmit` to 0 or 1 as required.
 
+#### Microsoft MCE class
+
+Class `MCE`. Example invocation:
+```python
+from ir_tx.mce import MCE
+# MCE.init_cs = 3
+```
+There is a separate demo for the `MCE` class because of the need to send a
+message on key release. It is run by issuing:
+```python
+from ir_tx.mcetest import test
+```
+Instructions will be displayed at the REPL.
+
+I have been unable to locate a definitive specification: the protocol was
+analysed by a mixture of googling and experiment. Behaviour may change if I
+acquire new information. The protocol is known as OrtekMCE and the remote
+control is sold on eBay as VRC-1100.
+
+The remote was designed for Microsoft Media Center and is used to control Kodi
+on boxes such as the Raspberry Pi. With a suitable PC driver it can emulate a
+PC keyboard and mouse. The mouse emulation uses a different protocol: the class
+does not currently support it. Pressing mouse buttons and pad will cause the
+error function (if provided) to be called.
+
+This supports a 4 bit address, 6 bit data and 2 bit toggle. The latter should
+have a value of 0 for the first message, 1 for repeat messages, and 2 for a
+final message sent on button release.
+
+The remaining four bits are a checksum which the driver creates. The algorithm
+requires an initial 'seed' value which my testing proved to be 4. However the
+only [documentation](http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE)
+I could find stated that the value should be 3. I implemented this as a class
+variable `MCE.init_cs=4`. This enables it to be changed if some receivers
+require 3.
+
 # 4. Principle of operation
 
 ## 4.1 Pyboard
@@ -163,8 +219,7 @@ This is performed by two hardware timers initiated in the constructor. Timer 2,
 channel 1 is used to configure the output pin as a PWM channel. Its frequency
 is set in the constructor. The OOK is performed by dynamically changing the
 duty ratio using the timer channel's `pulse_width_percent` method: this varies
-the pulse width from 0 to a duty ratio passed to the constructor. The NEC
-protocol defaults to 50%, the Sony and Philips ones to 30%.
+the pulse width from 0 to a duty ratio passed to the constructor.
 
 The duty ratio is changed by the Timer 5 callback `._cb`. This retrieves the
 next duration from the array. If it is not `STOP` it toggles the duty cycle
@@ -180,18 +235,7 @@ The carrier is generated by PWM instance `.pwm` running continuously. The ABC
 constructor converts the 0-100 duty ratio specified by the subclass to the
 0-1023 range used by ESP32.
 
-# 5. References
-
-[General information about IR](https://www.sbprojects.net/knowledge/ir/)
-
-The NEC protocol:  
-[altium](http://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol)  
-[circuitvalley](http://www.circuitvalley.com/2013/09/nec-protocol-ir-infrared-remote-control.html)
-
-Philips protocols:  
-[RC5](https://en.wikipedia.org/wiki/RC-5)  
-[RC5](https://www.sbprojects.net/knowledge/ir/rc5.php)  
-[RC6](https://www.sbprojects.net/knowledge/ir/rc6.php)
+## 4.3 Duty ratio
 
-Sony protocol:  
-[SIRC](https://www.sbprojects.net/knowledge/ir/sirc.php)
+In every case where I could find a specified figure it was 30%. I measured
+that from a variety of remotes, and in every case it was close to that figure.
index 0f094f9a5244ae10f41a06d874c41fd3473758e7..e8c6a1243b3174db935296a4f9d46b291d2546fa 100644 (file)
@@ -63,9 +63,9 @@ class IR_GET(IR_RX):
                     print('Philips RC-6 mode 0')
                     ok = True
 
-            if not ok and near(burst[0], 2056) and near(burst[1], 945):
+            if not ok and near(burst[0], 2000) and near(burst[1], 1000):
                 if near(duration, 19000):
-                    print('Microsoft MCE edition protocol. Not yet supported.')
+                    print('Microsoft MCE edition protocol.')
                     # Constant duration, variable burst length, presumably bi-phase
                     print('Protocol start {} {} Burst length {} duration {}'.format(burst[0], burst[1], lb, duration))
                     ok = True
@@ -104,10 +104,3 @@ def test():
     irg = IR_GET(pin)
     print('Waiting for IR data...')
     irg.acquire()
-
-# Yamaha NEC
-# Virgin RC-5
-
-# Samsung Unknown protocol 4576 4472 67 60755
-# Panasonic Unknown protocol 3526 1679 99 54303
-# Vista MCE edition Unknown protocol 2056 945 25 18935
diff --git a/ir_rx/mce.py b/ir_rx/mce.py
new file mode 100644 (file)
index 0000000..218c859
--- /dev/null
@@ -0,0 +1,69 @@
+# mce.py Decoder for IR remote control using synchronous code
+# Supports Microsoft MCE edition remote protocol.
+
+# Author: Peter Hinch
+# Copyright Peter Hinch 2020 Released under the MIT license
+
+# WARNING: This is experimental and subject to change.
+
+from utime import ticks_us, ticks_diff
+from ir_rx import IR_RX
+
+class MCE(IR_RX):
+    init_cs = 4  # http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE says 3
+    def __init__(self, pin, callback, *args):
+        # Block lasts ~19ms and has <= 34 edges
+        super().__init__(pin, 34, 25, callback, *args)
+
+    def decode(self, _):
+        def check(v):
+            if self.init_cs == -1:
+                return True
+            csum = v >> 12
+            cs = self.init_cs
+            for _ in range(12):
+                if v & 1:
+                    cs += 1
+                v >>= 1
+            return cs == csum
+
+        try:
+            t0 = ticks_diff(self._times[1], self._times[0])  # 2000μs mark
+            t1 = ticks_diff(self._times[2], self._times[1])  # 1000μs space
+            if not ((1800 < t0 < 2200) and (800 < t1 < 1200)):
+                raise RuntimeError(self.BADSTART)
+            nedges = self.edge  # No. of edges detected
+            if not 14 <= nedges <= 34:
+                raise RuntimeError(self.OVERRUN if nedges > 28 else self.BADSTART)
+            # Manchester decode
+            mask = 1
+            bit = 1
+            v = 0
+            x = 2
+            for _ in range(16):
+                # -1 convert count to index, -1 because we look ahead
+                if x > nedges - 2:
+                    raise RuntimeError(self.BADBLOCK)
+                # width is 500/1000 nominal
+                width = ticks_diff(self._times[x + 1], self._times[x])
+                if not 250 < width < 1350:
+                    self.verbose and print('Bad block 3 Width', width, 'x', x)
+                    raise RuntimeError(self.BADBLOCK)
+                short = int(width < 750)
+                bit ^= short ^ 1
+                v |= mask if bit else 0
+                mask <<= 1
+                x += 1 + short
+
+            self.verbose and print(bin(v))
+            print(bin(v))  # TEST
+            #if not check(v):
+                #raise RuntimeError(self.BADDATA)
+            val = (v >> 6) & 0x3f
+            addr = v & 0xf  # Constant for all buttons on my remote
+            ctrl = (v >> 4) & 3
+
+        except RuntimeError as e:
+            val, addr, ctrl = e.args[0], 0, 0
+        # Set up for new data burst and run user callback/error function
+        self.do_callback(val, addr, ctrl)
index cbd96f8e4de7eb1a2e58a1ba601d0e4e7b50a039..cd7feaa49d97873d7fa69d1b17ab7be9be0c32ba 100644 (file)
@@ -18,35 +18,30 @@ class RC5_IR(IR_RX):
             if not 14 <= nedges <= 28:
                 raise RuntimeError(self.OVERRUN if nedges > 28 else self.BADSTART)
             # Regenerate bitstream
-            bits = 0
+            bits = 1
             bit = 1
-            for x in range(1, nedges):
-                width = ticks_diff(self._times[x], self._times[x - 1])
-                if not 500 < width < 2000:
+            v = 1  # 14 bit bitstream, MSB always 1
+            x = 0
+            while bits < 14:
+                # -1 convert count to index, -1 because we look ahead
+                if x > nedges - 2:
+                    print('Bad block 1 edges', nedges, 'x', x)
                     raise RuntimeError(self.BADBLOCK)
-                for _ in range(1 if width < 1334 else 2):
-                    bits <<= 1
-                    bits |= bit
-                bit ^= 1
-            self.verbose and print(bin(bits))  # Matches inverted scope waveform
-            # Decode Manchester code
-            x = 30
-            while not bits >> x:
-                x -= 1
-            m0 = 1 << x  # Mask MS two bits (always 01)
-            m1 = m0 << 1
-            v = 0  # 14 bit bitstream
-            for _ in range(14):
-                v <<= 1
-                b0 = (bits & m0) > 0
-                b1 = (bits & m1) > 0
-                if b0 == b1:
+                # width is 889/1778 nominal
+                width = ticks_diff(self._times[x + 1], self._times[x])
+                if not 500 < width < 2100:
+                    self.verbose and print('Bad block 3 Width', width, 'x', x)
                     raise RuntimeError(self.BADBLOCK)
-                v |= b0
-                m0 >>= 2
-                m1 >>= 2
+                short = width < 1334
+                if not short:
+                    bit ^= 1
+                v <<= 1
+                v |= bit
+                bits += 1
+                x += 1 + int(short)
+            self.verbose and print(bin(v))
             # Split into fields (val, addr, ctrl)
-            val = (v & 0x3f) | (0x40 if ((v >> 12) & 1) else 0)
+            val = (v & 0x3f) | (0 if ((v >> 12) & 1) else 0x40)  # Correct the polarity of S2
             addr = (v >> 6) & 0x1f
             ctrl = (v >> 11) & 1
 
@@ -55,6 +50,7 @@ class RC5_IR(IR_RX):
         # Set up for new data burst and run user callback
         self.do_callback(val, addr, ctrl)
 
+
 class RC6_M0(IR_RX):
     # Even on Pyboard D the 444μs nominal pulses can be recorded as up to 705μs
     # Scope shows 360-520 μs (-84μs +76μs relative to nominal)
index 112db2ee9cc3291fe07a6d1b12981ea536fb81c5..9a5dacaa42ef2cbe392c900566368fc886c7c383 100644 (file)
@@ -1,5 +1,5 @@
 # test.py Test program for IR remote control decoder
-# Supports Pyboard ESP32 and ESP8266
+# Supports Pyboard, ESP32 and ESP8266
 
 # Author: Peter Hinch
 # Copyright Peter Hinch 2020 Released under the MIT license
@@ -15,6 +15,7 @@ from ir_rx.print_error import print_error  # Optional print of error codes
 from ir_rx.nec import NEC_8, NEC_16
 from ir_rx.sony import SONY_12, SONY_15, SONY_20
 from ir_rx.philips import RC5_IR, RC6_M0
+from ir_rx.mce import MCE
 
 # Define pin according to platform
 if platform == 'pyboard':
@@ -23,7 +24,7 @@ elif platform == 'esp8266':
     freq(160000000)
     p = Pin(13, Pin.IN)
 elif platform == 'esp32' or platform == 'esp32_LoBo':
-    p = Pin(23, Pin.IN)  # was 27
+    p = Pin(23, Pin.IN)
 
 # User callback
 def cb(data, addr, ctrl):
@@ -33,19 +34,22 @@ def cb(data, addr, ctrl):
         print('Data {:02x} Addr {:04x} Ctrl {:02x}'.format(data, addr, ctrl))
 
 def test(proto=0):
-    classes = (NEC_8, NEC_16, SONY_12, SONY_15, SONY_20, RC5_IR, RC6_M0)
+    classes = (NEC_8, NEC_16, SONY_12, SONY_15, SONY_20, RC5_IR, RC6_M0, MCE)
     ir = classes[proto](p, cb)  # Instantiate receiver
     ir.error_function(print_error)  # Show debug information
     #ir.verbose = True
     # A real application would do something here...
-    while True:
-        print('running')
-        time.sleep(5)
-        gc.collect()
+    try:
+        while True:
+            print('running')
+            time.sleep(5)
+            gc.collect()
+    except KeyboardInterrupt:
+        ir.close()
 
 # **** DISPLAY GREETING ****
 s = '''Test for IR receiver. Run:
-from ir_rx import test
+from ir_rx.test import test
 test() for NEC 8 bit protocol,
 test(1) for NEC 16 bit,
 test(2) for Sony SIRC 12 bit,
@@ -53,6 +57,7 @@ test(3) for Sony SIRC 15 bit,
 test(4) for Sony SIRC 20 bit,
 test(5) for Philips RC-5 protocol,
 test(6) for RC6 mode 0.
+test(7) for Microsoft Vista MCE.
 
 Hit ctrl-c to stop, then ctrl-d to soft reset.'''
 
index 6fe771048d3d535c921d6aec0185580b70609f0f..5f0071871abfab5e7c6b3efa1393a24cf204ef04 100644 (file)
@@ -18,9 +18,7 @@ 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
+# Duty ratio in carrier off state.
 _SPACE = const(0)
 # On ESP32 gate hardware design is led_on = rmt and carrier
 
@@ -31,8 +29,11 @@ STOP = const(0)  # End of data
 # 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.
 class IR:
+    active_high = True  # Hardware turns IRLED on if pin goes high.
 
     def __init__(self, pin, cfreq, asize, duty, verbose):
+        if not IR.active_high:
+             duty = 100 - duty
         if ESP32:
             self._pwm = PWM(pin[0])  # Continuous 36/38/40KHz carrier
             self._pwm.deinit()
@@ -44,7 +45,7 @@ class IR:
             self._ch = tim.channel(1, Timer.PWM, pin=pin)
             self._ch.pulse_width_percent(_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)
diff --git a/ir_tx/mce.py b/ir_tx/mce.py
new file mode 100644 (file)
index 0000000..24f1aef
--- /dev/null
@@ -0,0 +1,43 @@
+# mce.py Encoder for IR remote control using synchronous code
+# Supports Microsoft MCE edition remote protocol.
+
+# Author: Peter Hinch
+# Copyright Peter Hinch 2020 Released under the MIT license
+
+# WARNING: This is experimental and subject to change.
+
+from micropython import const
+from ir_tx import IR
+
+_TBIT = const(500)  # Time (μs) for pulse of carrier
+
+
+class MCE(IR):
+    init_cs = 4  # http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE says 3
+
+    def __init__(self, pin, freq=38000, verbose=False):
+        super().__init__(pin, freq, 34, 30, verbose)
+
+    def tx(self, addr, data, toggle):
+        def checksum(v):
+            cs = self.init_cs
+            for _ in range(12):
+                if v & 1:
+                    cs += 1
+                v >>= 1
+            return cs
+
+        self.append(2000, 1000, _TBIT)
+        d = ((data & 0x3f) << 6) | (addr & 0xf)  | ((toggle & 3) << 4)
+        d |= checksum(d) << 12
+        self.verbose and print(bin(d))
+
+        mask = 1
+        while mask < 0x10000:
+            bit = bool(d & mask)
+            if bit ^ self.carrier:
+                self.add(_TBIT)
+                self.append(_TBIT)
+            else:
+                self.append(_TBIT, _TBIT)
+            mask <<= 1
diff --git a/ir_tx/mcetest.py b/ir_tx/mcetest.py
new file mode 100644 (file)
index 0000000..2b4af96
--- /dev/null
@@ -0,0 +1,99 @@
+# ir_tx.mcetest Test for nonblocking MCE IR blaster.
+
+# Released under the MIT License (MIT). See LICENSE.
+
+# Copyright (c) 2020 Peter Hinch
+
+# Implements a 2-button remote control on a Pyboard with auto repeat.
+from sys import platform
+ESP32 = platform == 'esp32'
+if ESP32:
+    from machine import Pin
+else:
+    from pyb import Pin, LED
+
+from micropython import const
+import uasyncio as asyncio
+from aswitch import Switch, Delay_ms
+from ir_tx.mce import MCE
+
+loop = asyncio.get_event_loop()
+_FIRST = const(0)
+_REP = const(1)
+_END = const(2)
+_REP_DELAY = const(60)
+
+class Rbutton:
+    def __init__(self, irb, pin, addr, data, rep_code=False):
+        self.irb = irb
+        self.sw = Switch(pin)
+        self.addr = addr
+        self.data = data
+        self.rep_code = rep_code
+        self.sw.close_func(self.cfunc)
+        self.sw.open_func(self.ofunc)
+        self.tim = Delay_ms(self.repeat)
+        self.stop = False
+
+    def cfunc(self):  # Button push: send data and set up for repeats
+        print('start')
+        self.irb.transmit(self.addr, self.data, _FIRST)
+        self.tim.trigger(_REP_DELAY)
+
+    def ofunc(self):  # Button release: cancel repeat timer
+        self.stop = True
+
+    async def repeat(self):
+        await asyncio.sleep(0)  # Let timer stop before retriggering
+        if self.stop:  # Button has been released: send last message
+            self.stop = False
+            self.tim.stop()  # Not strictly necessary
+            self.irb.transmit(self.addr, self.data, _END)
+            print('stop')
+        else:
+            print('rep')
+            self.tim.trigger(_REP_DELAY)
+            self.irb.transmit(self.addr, self.data, _REP)
+
+async def main():
+    if ESP32:  # Pins for IR LED gate
+        pin = (Pin(23, Pin.OUT, value = 0), Pin(21, Pin.OUT, value = 0))
+    else:
+        pin = Pin('X1')
+    irb = MCE(pin, verbose=True)
+
+    b = []  # Rbutton instances
+    px3 = Pin(18, Pin.IN, Pin.PULL_UP) if ESP32 else Pin('X3', Pin.IN, Pin.PULL_UP)
+    px4 = Pin(19, Pin.IN, Pin.PULL_UP) if ESP32 else Pin('X4', Pin.IN, Pin.PULL_UP)
+    b.append(Rbutton(irb, px3, 0x1, 0x7))
+    b.append(Rbutton(irb, px4, 0xe, 0xb))
+    if ESP32:
+        while True:
+            print('Running')
+            await asyncio.sleep(5)
+    else:
+        led = LED(1)
+        while True:
+            await asyncio.sleep_ms(500)  # Obligatory flashing LED.
+            led.toggle()
+
+# Greeting strings. Common:
+s = '''Test for IR transmitter. Run:
+from ir_tx.mcetest import test
+test()
+'''
+# Pyboard:
+spb = '''
+IR LED on pin X1
+Ground pin X3 to send addr 1 data 7
+Ground pin X4 to send addr 0xe data 0x0b.'''
+# ESP32
+sesp = '''
+IR LED gate on pins 23, 21
+Ground pin 18 to send addr 1 data 7
+Ground pin 19 to send addr 0xe data 0x0b.'''
+
+print(''.join((s, sesp)) if ESP32 else ''.join((s, spb)))
+
+def test():
+    loop.run_until_complete(main())
index 1e02c9f9068acb74b442dfd2f85e2399ce47fac6..10cc67bfe164b72bcb5822dbbc02af3aefa3caf9 100644 (file)
@@ -13,7 +13,7 @@ _T_ONE = const(1687)
 class NEC(IR):
 
     def __init__(self, pin, freq=38000, verbose=False):  # NEC specifies 38KHz
-        super().__init__(pin, freq, 68, 50, verbose)
+        super().__init__(pin, freq, 68, 33, verbose)  # Measured duty ratio 33%
 
     def _bit(self, b):
         self.append(_TBURST, _T_ONE if b else _TBURST)
index a37dd5b282217c0ed5bce634af432905ba95544f..3ca270ae746fd79734029a4b9b22fb51566dd32b 100644 (file)
@@ -5,19 +5,19 @@
 # Copyright Peter Hinch 2020 Released under the MIT license
 
 from micropython import const
-from sys import platform
 from ir_tx import IR
 
 # Philips RC5 protocol
 _T_RC5 = const(889)  # Time for pulse of carrier
-ermsg = 'ESP32 does not support Philips protocols'
+
+
 class RC5(IR):
 
     def __init__(self, pin, freq=36000, verbose=False):
         super().__init__(pin, freq, 28, 30, verbose)
 
-    def tx(self, addr, data, toggle):
-        d = (data & 0x3f) | ((addr & 0x1f) << 6) | ((data & 0x40) << 6) | ((toggle & 1) << 11)
+    def tx(self, addr, data, toggle):  # Fix RC5X S2 bit polarity
+        d = (data & 0x3f) | ((addr & 0x1f) << 6) | (((data & 0x40) ^ 0x40) << 6) | ((toggle & 1) << 11)
         self.verbose and print(bin(d))
         mask = 0x2000
         while mask:
index b175dcd2ef8fec1a87be167140448debd5df45b4..9e199dd6a886ed8e86c3ba1ea6bbe22b20a5d923 100644 (file)
@@ -1,4 +1,4 @@
-# ir_tx_test.py Test for nonblocking NEC/SONY/RC-5/RC-6 mode 0 IR blaster.
+# ir_tx.test Test for nonblocking NEC/SONY/RC-5/RC-6 mode 0 IR blaster.
 
 # Released under the MIT License (MIT). See LICENSE.
 
@@ -80,7 +80,7 @@ async def main(proto):
 
 # Greeting strings. Common:
 s = '''Test for IR transmitter. Run:
-from ir_tx_test import test
+from ir_tx.test import test
 test() for NEC protocol
 test(1) for Sony SIRC 12 bit
 test(2) for Sony SIRC 15 bit