]> vault307.fbx.one Git - micorpython_ir.git/commitdiff
Sony complete.
authorPeter Hinch <peter@hinch.me.uk>
Thu, 27 Feb 2020 10:00:53 +0000 (10:00 +0000)
committerPeter Hinch <peter@hinch.me.uk>
Thu, 27 Feb 2020 10:00:53 +0000 (10:00 +0000)
README.md
ir_rx.py
ir_rx_test.py
ir_tx.py
ir_tx_test.py

index d90a74f0145a9d61606ef4f60022ab529fc7badd..2715f4267ec7f8324ccc61a7427514f1837aeb33 100644 (file)
--- a/README.md
+++ b/README.md
@@ -8,13 +8,14 @@ require `uasyncio` but are compatible with it.
 
 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
 
 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 two options for carrier frequency, namely 36KHz and 38KHz.
+least three options for carrier frequency, namely 36KHz, 38KHz and 40KHz.
 
 
-The drivers support the NEC protocol and 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 must be specified for the correct frequency. The receiver device driver
-sees the demodulated signal and is hence carrier frequency agnostic.
+The drivers support NEC and Sony protocols and 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 specified for the correct frequency. The receiver
+device driver sees the demodulated signal and is hence carrier frequency
+agnostic.
 
 Examining waveforms from various remote controls it is evident that numerous
 protocols exist. Some are doubtless proprietary and undocumented. The supported
 
 Examining waveforms from various remote controls it is evident that numerous
 protocols exist. Some are doubtless proprietary and undocumented. The supported
@@ -41,6 +42,9 @@ demodulates the 38KHz IR pulses and passes the demodulated pulse train to the
 microcontroller. The tested chip returns a 0 level on carrier detect, but the
 driver design should ensure operation regardless of sense.
 
 microcontroller. The tested chip returns a 0 level on carrier detect, but the
 driver design should ensure operation regardless of sense.
 
+In my testing a 38KHz demodulator worked with 36KHz and 40KHz remotes, but this
+is obviously not guaranteed or optimal.
+
 The pin used to connect the decoder chip to the target is arbitrary but the
 test programs assume pin X3 on the Pyboard, pin 13 on the ESP8266 and pin 23 on
 ESP32.
 The pin used to connect the decoder chip to the target is arbitrary but the
 test programs assume pin X3 on the Pyboard, pin 13 on the ESP8266 and pin 23 on
 ESP32.
@@ -84,22 +88,32 @@ from the official library and `aswitch.py` from
 
 # 4. Receiver
 
 
 # 4. Receiver
 
-This implements a class for each supported protocol, namely `NEC_IR`, `RC5_IR`
-and `RC6_M0`. Applications should instantiate the appropriate class with a
-callback. The callback will run whenever an IR pulsetrain is received.
+This implements a class for each supported protocol, namely `NEC_IR`,
+`SONY_IR`, `RC5_IR` and `RC6_M0`. Applications should instantiate the
+appropriate class with a callback. The callback will run whenever an IR pulse
+train is received.
 
 Constructor:  
 `NEC_IR` args: `pin`, `callback`, `extended=True`, `*args`  
 
 Constructor:  
 `NEC_IR` args: `pin`, `callback`, `extended=True`, `*args`  
+`SONY_IR` args: `pin`, `callback`, `bits=20`, `*args`  
 `RC5_IR` and `RC6_M0`: args `pin`, `callback`, `*args`  
 `RC5_IR` and `RC6_M0`: args `pin`, `callback`, `*args`  
-Args:  
+
+Args (all protocols):  
  1. `pin` is a `machine.Pin` instance configured as an input, connected to the
  IR decoder chip.  
  2. `callback` is the user supplied callback (see below).
  1. `pin` is a `machine.Pin` instance configured as an input, connected to the
  IR decoder chip.  
  2. `callback` is the user supplied callback (see below).
- 3. `extended` is an NEC specific boolean. Remotes using the NEC protocol can
+ 4. `*args` Any further args will be passed to the callback.  
+
+Protocol specific args:
+ 1. `extended` is an NEC specific boolean. Remotes using the NEC protocol can
  send 8 or 16 bit addresses. If `True` 16 bit addresses are assumed - an 8 bit
  address will be correctly received. Set `False` to enable extra error checking
  for remotes that return an 8 bit address.
  send 8 or 16 bit addresses. If `True` 16 bit addresses are assumed - an 8 bit
  address will be correctly received. Set `False` to enable extra error checking
  for remotes that return an 8 bit address.
- 4. `*args` Any further args will be passed to the callback.  
+ 2. `bits=20` Sony specific. The SIRC protocol comes in 3 variants: 12, 15 and
+ 20 bits. The default will handle bitstreams from all three types of remote.
+ Choosing a value matching your remote improves the timing and reduces the
+ likelihood of errors when handling repeats: the SIRC timing when a button is
+ held down is tight in 20 bit mode.
 
 The callback takes the following args:  
  1. `data` Integer value fom the remote. A negative value indicates an error
 
 The callback takes the following args:  
  1. `data` Integer value fom the remote. A negative value indicates an error
@@ -174,8 +188,8 @@ for a repeat code. Philips codes: RC-5 900μs, RC-6 mode 0 5.5ms.
 
 This is specific to Pyboard D and Pyboard 1.x (not Lite).
 
 
 This is specific to Pyboard D and Pyboard 1.x (not Lite).
 
-It implements a class for each supported protocol, namely `NEC`, `RC5` and
-`RC6_M0`. The application instantiates the appropriate class and calls the
+It implements a class for each supported protocol, namely `NEC`, `SONY`, `RC5`
+and `RC6_M0`. The application instantiates the appropriate class and calls the
 `transmit` method to send data.
 
 Constructor  
 `transmit` method to send data.
 
 Constructor  
@@ -184,18 +198,29 @@ All constructors take the following args:
  is employed by the test script. Must be connected to the IR diode as described
  below.
  2. `freq=default` The carrier frequency in Hz. The default for NEC is 38000,
  is employed by the test script. Must be connected to the IR diode as described
  below.
  2. `freq=default` The carrier frequency in Hz. The default for NEC is 38000,
and for Philips is 36000.
Sony is 40000 and Philips is 36000.
  3. `verbose=False` If `True` emits debug output.
 
  3. `verbose=False` If `True` emits debug output.
 
+The `SONY` constructor is of form `pin, bits=12, freq=40000, verbose=False`.
+The `bits` value may be 12, 15 or 20 to set the highest SIRC variant in use.
+Other args are as above. If `bits` is set to 20 then all variants will be
+received. Setting the value to the maximum expected improves error checking and
+timing tolerances. In particular a worst-case 20-bit block takes 39ms nominal,
+yet the repeat time is 45ms nominal.
+
+The Sony remote tested issues both 12 bit and 15 bit streams.
+
 Method:
  1. `transmit(addr, data, toggle=0)` Integer args. `addr` and `data` are
 Method:
  1. `transmit(addr, data, toggle=0)` Integer args. `addr` and `data` are
- normally 8-bit values and `toggle` is 0 or 1.  
+ normally 8-bit values and `toggle` is normally 0 or 1.  
  In the case of NEC, if an address < 256 is passed, normal mode is assumed and
  the complementary value is appended. 16-bit values are transmitted as extended
  addresses.  
  In the case of NEC the `toggle` value is ignored. For Philips protocols it
  should be toggled each time a button is pressed, and retained if the button is
  In the case of NEC, if an address < 256 is passed, normal mode is assumed and
  the complementary value is appended. 16-bit values are transmitted as extended
  addresses.  
  In the case of NEC the `toggle` value is ignored. For Philips protocols it
  should be toggled each time a button is pressed, and retained if the button is
- held down. The test program illustrates a way to do this.
+ held down. The test program illustrates a way to do this.  
+ `SONY` ignores `toggle` unless in 20-bit mode, in which case it is transmitted
+ as the `extended` value and can be any integer in range 0 to 255.
 
 The `transmit` method is synchronous with rapid return. Actual transmission
 occurs as a background process, controlled by timers 2 and 5. Execution times
 
 The `transmit` method is synchronous with rapid return. Actual transmission
 occurs as a background process, controlled by timers 2 and 5. Execution times
@@ -222,7 +247,7 @@ which is the driver default. If using a circuit where "off" is required to be
 
 The classes inherit from the abstract base class `IR`. This has an array `.arr`
 to contain the duration (in μs) of each carrier on or off period. The
 
 The classes inherit from the abstract base class `IR`. This has an array `.arr`
 to contain the duration (in μs) of each carrier on or off period. The
-`transmit` method calls a `tx` method in the subclass which populates this
+`transmit` method calls a `tx` method of the subclass which populates this
 array. On completion `transmit` appends a special `STOP` value and initiates
 physical transmission which occurs in an interrupt context.
 
 array. On completion `transmit` appends a special `STOP` value and initiates
 physical transmission which occurs in an interrupt context.
 
index 6c484bc26c100cf0d9958e331af1aecba74d59e3..d73016a240a56d754e316fff678e4a196e90da9a 100644 (file)
--- a/ir_rx.py
+++ b/ir_rx.py
@@ -115,6 +115,58 @@ class NEC_IR(IR_RX):
         self.edge = 0  # Set up for new data burst and run user callback
         self.callback(cmd, addr, 0, *self.args)
 
         self.edge = 0  # Set up for new data burst and run user callback
         self.callback(cmd, addr, 0, *self.args)
 
+
+class SONY_IR(IR_RX):
+    def __init__(self, pin, callback, bits=20, *args):
+        # 20 bit block has 42 edges and lasts <= 39ms nominal. Add 4ms to time
+        # for tolerances except in 20 bit case where timing is tight with a
+        # repeat period of 45ms.
+        t = int(3 + bits * 1.8) + (1 if bits == 20 else 4)
+        super().__init__(pin, 2 + bits * 2, t, callback, *args)
+        self._addr = 0
+        self._bits = bits
+
+    def decode(self, _):
+        try:
+            nedges = self.edge  # No. of edges detected
+            print(nedges)
+            if nedges > 42:
+                raise RuntimeError(OVERRUN)
+            bits = (nedges - 2) // 2
+            if nedges not in (26, 32, 42) or bits > self._bits:
+                raise RuntimeError(BADBLOCK)
+            self.verbose and print('SIRC {}bit'.format(bits))
+            width = ticks_diff(self._times[1], self._times[0])
+            if not 1800 < width < 3000:  # 2.4ms leading mark for all valid data
+                raise RuntimeError(BADSTART)
+            width = ticks_diff(self._times[2], self._times[1])
+            if not 350 < width < 1000:  # 600μs space
+                raise RuntimeError(BADSTART)
+
+            val = 0  # Data received, LSB 1st
+            x = 2
+            bit = 1
+            while x < nedges - 2:
+                if ticks_diff(self._times[x + 1], self._times[x]) > 900:
+                    val |= bit
+                bit <<= 1
+                x += 2
+
+            cmd = val & 0x7f  # 7 bit command
+            val >>= 7
+            if nedges < 42:
+                addr = val & 0xff  # 5 or 8 bit addr
+                val = 0
+            else:
+                addr = val & 0x1f  # 5 bit addr
+                val >>= 5  # 8 bit extended
+        except RuntimeError as e:
+            cmd = e.args[0]
+            addr = 0
+            val = 0
+        self.edge = 0  # Set up for new data burst and run user callback
+        self.callback(cmd, addr, val, *self.args)
+
 class RC5_IR(IR_RX):
     def __init__(self, pin, callback, *args):
         # Block lasts <= 30ms and has <= 28 edges
 class RC5_IR(IR_RX):
     def __init__(self, pin, callback, *args):
         # Block lasts <= 30ms and has <= 28 edges
index 82f97dd0862cedbdbc265f1ad04a215bdc0e1932..2785398cefbccb39537156312947b2590e5ae786 100644 (file)
@@ -31,13 +31,17 @@ def cb(data, addr, ctrl):
     elif data >= 0:
         print('Data {:03x} Addr {:03x} Ctrl {:01x}'.format(data, addr, ctrl))
     else:
     elif data >= 0:
         print('Data {:03x} Addr {:03x} Ctrl {:01x}'.format(data, addr, ctrl))
     else:
-        print('{} Address: {}'.format(errors[data], hex(addr)))
+        print(errors[data])  # Application would ignore errors
 
 
 s = '''Test for IR receiver. Run:
 
 
 s = '''Test for IR receiver. Run:
-ir_tx_test.test() for NEC protocol,
-ir_tx_test.test(5) for Philips RC-5 protocol,
-ir_tx_test.test(6) for RC6 mode 0.
+from ir_rx_test import test
+test() for NEC protocol,
+test(1) for Sony SIRC 12 bit,
+test(2) for Sony SIRC 15 bit,
+test(3) for Sony SIRC 20 bit,
+test(5) for Philips RC-5 protocol,
+test(6) for RC6 mode 0.
 
 Background processing means REPL prompt reappears.
 Hit ctrl-D to stop (soft reset).'''
 
 Background processing means REPL prompt reappears.
 Hit ctrl-D to stop (soft reset).'''
@@ -47,6 +51,10 @@ print(s)
 def test(proto=0):
     if proto == 0:
         ir = NEC_IR(p, cb)  # Extended mode
 def test(proto=0):
     if proto == 0:
         ir = NEC_IR(p, cb)  # Extended mode
+    elif proto < 4:
+        bits = (12, 15, 20)[proto - 1]
+        ir = SONY_IR(p, cb, bits)
+        ir.verbose = True
     elif proto == 5:
         ir = RC5_IR(p, cb)
     elif proto == 6:
     elif proto == 5:
         ir = RC5_IR(p, cb)
     elif proto == 6:
index a1cc9e9859d03a4d2d64b1b7da711f8eb7f79ceb..4a91336680394e324f9a9815a3156ce4dfd07448 100644 (file)
--- a/ir_tx.py
+++ b/ir_tx.py
@@ -100,6 +100,30 @@ class NEC(IR):
         self.aptr = 0  # Reset pointer
         self.cb(self._tim)  # Initiate physical transmission.
 
         self.aptr = 0  # Reset pointer
         self.cb(self._tim)  # Initiate physical transmission.
 
+# NEC protocol
+class SONY(IR):
+
+    def __init__(self, pin, bits=12, freq=40000, verbose=False):  # Sony specifies 40KHz
+        super().__init__(pin, freq, 3 + bits * 2, 30, verbose)
+        if bits not in (12, 15, 20):
+            raise ValueError('bits must be 12, 15 or 20.')
+        self.bits = bits
+
+    def tx(self, addr, data, ext):
+        self.append(2400, 600)
+        bits = self.bits
+        v = data & 0x7f
+        if bits == 12:
+            v |= (addr & 0x1f) << 7
+        elif bits == 15:
+            v |= (addr & 0xff) << 7
+        else:
+            v |= (addr & 0x1f) << 7
+            v |= (ext & 0xff) << 12
+        for _ in range(bits):
+            self.append(1200 if v & 1 else 600, 600)
+            v >>= 1
+
 
 # Philips RC5 protocol
 class RC5(IR):
 
 # Philips RC5 protocol
 class RC5(IR):
index 08230a38f8cbcbe29d88ca496a6e76ddff7e0d13..ad55a22acb181d912218968f23acc9b8d4bed069 100644 (file)
@@ -1,15 +1,15 @@
-# ir_tx_test.py Test for nonblocking NEC/RC-5/RC-6 mode 0 IR blaster.
+# ir_tx_test.py Test for nonblocking NEC/SONY/RC-5/RC-6 mode 0 IR blaster.
 
 # Released under the MIT License (MIT). See LICENSE.
 
 # Copyright (c) 2020 Peter Hinch
 
 
 # Released under the MIT License (MIT). See LICENSE.
 
 # Copyright (c) 2020 Peter Hinch
 
-# Implements a 2-button remote control on a Pyboard
+# Implements a 2-button remote control on a Pyboard with auto repeat.
 
 from pyb import Pin, LED
 import uasyncio as asyncio
 from aswitch import Switch, Delay_ms
 
 from pyb import Pin, LED
 import uasyncio as asyncio
 from aswitch import Switch, Delay_ms
-from ir_tx import NEC, RC5, RC6_M0
+from ir_tx import NEC, SONY, RC5, RC6_M0
 
 loop = asyncio.get_event_loop()
 
 
 loop = asyncio.get_event_loop()
 
@@ -27,7 +27,8 @@ class Rbutton:
 
     def cfunc(self):  # Button push: send data
         self.irb.transmit(self.addr, self.data, Rbutton.toggle)
 
     def cfunc(self):  # Button push: send data
         self.irb.transmit(self.addr, self.data, Rbutton.toggle)
-        # Auto repeat
+        # Auto repeat. The Sony protocol specifies 45ms but this is tight.
+        # In 20 bit mode a data burst can be upto 39ms long.
         self.tim.trigger(108)
 
     def ofunc(self):  # Button release: cancel repeat timer
         self.tim.trigger(108)
 
     def ofunc(self):  # Button release: cancel repeat timer
@@ -53,6 +54,9 @@ async def main(proto):
         irb = NEC(pin)  # Default NEC freq == 38KHz
         # Option to send REPEAT code. Most remotes do this.
         rep_code = True
         irb = NEC(pin)  # Default NEC freq == 38KHz
         # Option to send REPEAT code. Most remotes do this.
         rep_code = True
+    elif proto < 4:
+        bits = (12, 15, 20)[proto - 1]
+        irb = SONY(pin, bits, 38000)  # My decoder chip is 38KHz
     elif proto == 5:
         irb = RC5(pin, 38000)  # My decoder chip is 38KHz
     elif proto == 6:
     elif proto == 5:
         irb = RC5(pin, 38000)  # My decoder chip is 38KHz
     elif proto == 6:
@@ -67,9 +71,13 @@ async def main(proto):
         led.toggle()
 
 s = '''Test for IR transmitter. Run:
         led.toggle()
 
 s = '''Test for IR transmitter. Run:
-ir_tx_test.test() for NEC protocol
-ir_tx_test.test(5) for RC-5 protocol
-ir_tx_test.test(6) for RC-6 mode 0.
+from ir_tx_test import test
+test() for NEC protocol
+test(1) for Sony SIRC 12 bit
+test(2) for Sony SIRC 15 bit
+test(3) for Sony SIRC 20 bit
+test(5) for Philips RC-5 protocol
+test(6) for Philips RC-6 mode 0.
 
 Ground X3 to send addr 1 data 7
 Ground X4 to send addr 0x10 data 0x0b.'''
 
 Ground X3 to send addr 1 data 7
 Ground X4 to send addr 0x10 data 0x0b.'''