]> vault307.fbx.one Git - micorpython_ir.git/blobdiff - TRANSMITTER.md
Transmitter: ESP32 uses RMT to generate carrier. Test program updated for uasyncio V3
[micorpython_ir.git] / TRANSMITTER.md
index 8a8331414ee1d726e1ddb87e1ec0b40da2ff1211..7a70ccae6fc687121b30496f3179ceb27e5defef 100644 (file)
@@ -13,8 +13,8 @@ On the Pyboard the transmitter test script assumes pin X1 for IR output. It can
 be changed, but it must support Timer 2 channel 1. Pins for pushbutton inputs
 are arbitrary: X3 and X4 are used. The driver uses timers 2 and 5.
 
 be changed, but it must support Timer 2 channel 1. Pins for pushbutton inputs
 are arbitrary: X3 and X4 are used. The driver uses timers 2 and 5.
 
-On ESP32 the demo uses pins 21 and 23 for IR output and pins 18 and 19 for
-pushbuttons. These pins may be changed.
+On ESP32 the demo uses pin 23 for IR output and pins 18 and 19 for pushbuttons.
+These pins may be changed. The only device resource used is `RMT(0)`.
 
 ## 1.1 Pyboard Wiring
 
 
 ## 1.1 Pyboard Wiring
 
@@ -30,20 +30,16 @@ increase power.
 The transistor type is not critical.
 
 The driver assumes circuits as shown. Here the carrier "off" state is 0V,
 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.
+which is the driver default. If using an alternative circuit where "off" is
+required to be 3.3V, the class variable `active_high` should be set `False`.
 
 ## 1.2 ESP32 Wiring
 
 
 ## 1.2 ESP32 Wiring
 
-The ESP32 RMT device does not currently support the carrier option. A simple
-hardware gate is required to turn the IR LED on when both the carrier pin and
-the RMT pin are high. A suitable circuit is below.  
-![Image](images/gate.png)
-
-The transistor type is not critical. A gate could be built with two similarly
-connected N-channel MOSFETS. The 1KΩ resistors would not be required. The
-MOSFETS would require a low RDSon at Vgs == 3.3V. A ZVN4210A seems suitable
-but I haven't tried it.
+The ESP32 RMT device now supports the carrier option, and this driver has been
+updated to use it. The same circuits as above may be used to connect to pin 23
+(or other pin, if the code has been adapted). The `active_high` option is not
+available on the ESP32 `RMT` object, so any alternative circuit must illuminate
+the LED if the pin state is high.
 
 # 2. Dependencies and installation
 
 
 # 2. Dependencies and installation
 
@@ -51,16 +47,26 @@ but I haven't tried it.
 
 The device driver has no dependencies.
 
 
 The device driver has no dependencies.
 
-On ESP32 a firmware version >= V1.12 is required. The Loboris port is not
-supported owing to the need for the RMT device.
+On ESP32 a firmware version >= V1.14 is required. The Loboris port is not
+supported owing to the need for the RMT device and other issues.
 
 
-The demo program requires `uasyncio` from the official library and `aswitch.py`
-from [this repo](https://github.com/peterhinch/micropython-async).
+The demo program uses `uasyncio` primitives from
+[this repo](https://github.com/peterhinch/micropython-async). Clone the repo to
+a directory on your PC:
+```bash
+$ git clone https://github.com/peterhinch/micropython-async
+```
+move to its `v3` directory, and copy the `primitives` directory with its
+contents to the filesystem.
 
 ## 2.2 Installation
 
 The transmitter is a Python package. This minimises RAM usage: applications
 
 ## 2.2 Installation
 
 The transmitter is a Python package. This minimises RAM usage: applications
-only import the device driver for the protocol in use.
+only import the device driver for the protocol in use. Clone the repository to
+the current directory of your PC with:
+```bash
+$ git clone https://github.com/peterhinch/micropython_ir
+```
 
 Copy the following to the target filesystem:
  1. `ir_tx` Directory and contents.
 
 Copy the following to the target filesystem:
  1. `ir_tx` Directory and contents.
@@ -81,6 +87,21 @@ It implements a class for each supported protocol, namely `NEC`, `SONY_12`,
 common abstract base class in `__init__.py`. The application instantiates the
 appropriate class and calls the `transmit` method to send data.
 
 common abstract base class in `__init__.py`. The application instantiates the
 appropriate class and calls the `transmit` method to send data.
 
+Basic usage on a Pyboard:
+```python
+from machine import Pin
+from ir_tx.nec import NEC
+nec = NEC(Pin('X1'))
+nec.transmit(1, 2)  # address == 1, data == 2
+```
+Basic usage on ESP32:
+```python
+from machine import Pin
+from ir_tx.nec import NEC
+nec = NEC(Pin(23, Pin.OUT, value = 0))
+nec.transmit(1, 2)  # address == 1, data == 2
+```
+
 #### Common to all classes
 
 Constructor args:  
 #### Common to all classes
 
 Constructor args:  
@@ -93,17 +114,48 @@ Constructor args:
  3. `verbose=False` If `True` emits (a lot of) debug output.
 
 Method:
  3. `verbose=False` If `True` emits (a lot of) debug output.
 
 Method:
- 1. `transmit(addr, data, toggle=0)` Integer args. `addr` and `data` are
- normally 8-bit values and `toggle` is normally 0 or 1; details are protocol
- dependent and are described below.
+ 1. `transmit(addr, data, toggle=0, validate=False)` Args `addr`, `data` and
+ `toggle` are positive integers. The maximum vaues are protocol dependent. If
+ `validate` is `True` passed values are checked and a `ValueError` raised if
+ they are out of range. If `validate` is false invalid bits are silently
+ discarded. For example if an address of 0x11 is passed to `MCE.transmit`, the
+ address sent will be 1 because that protocol supports only a four bit address
+ field. The `toggle` field is unused by some protocols when 0 should be passed.
+
+Class method:
+ 1. `active_low` No args. Pyboard only. A `ValueError` will be thrown on ESP32.
+ The IR LED drive circuit is usually designed to turn the LED on if the driver
+ pin is high. If it has opposite polarity the method must be called before
+ instantiating the class - it will be ineffective if called later.
+
+Class varaible:
+ 1. `timeit=False` If `True` the `.transmit` method times itself and prints the
+ result in μs.
 
 The `transmit` method is synchronous with rapid return. Actual transmission
 
 The `transmit` method is synchronous with rapid return. Actual transmission
-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.
+occurs as a background process, on the Pyboard controlled by timers 2 and 5. On
+ESP32 the RMT class is used. Execution times were measured on a Pyboard 1.1 and
+the ESP32 reference board  without SPIRAM. Tests were done at stock frequency and
+with `validate=True`, `verbose=False`. A small saving could be achieved by
+skipping validation.
+
+| Protocol | ESP32 | Pyboard |
+|:--------:|:-----:|:-------:|
+| NEC      | 7.8ms | 3.2ms   |
+| SONY12   | 3.2ms | 1.3ms   |
+| SONY15   | 3.6ms | 1.5ms   |
+| SONY20   | 4.5ms | 1.9ms   |
+| RC5      | 4.9ms | 1.5ms   |
+| RC6_M0   | 6.0ms | 2.0ms   |
+| MCE      | 6.7ms | 2.0ms   |
 
 #### NEC class
 
 
 #### 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.
 
 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.
 
@@ -117,6 +169,11 @@ A value passed in `toggle` is ignored.
 
 #### Sony classes
 
 
 #### 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`
 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`
@@ -128,6 +185,11 @@ value.
 
 #### Philips classes
 
 
 #### 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.
 
 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.
 
@@ -137,9 +199,43 @@ 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.
 
 changes when the button is released. The application should implement this
 behaviour, setting the `toggle` arg of `.transmit` to 0 or 1 as required.
 
-# 4. Principle of operation
+#### Microsoft MCE class
 
 
-## 4.1 Pyboard
+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
 
 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
@@ -152,42 +248,85 @@ this is required for bi-phase (manchester) codings.
 The `.add` method takes a single μs time value and adds it to the last value
 in the array: this pulse lengthening is used in bi-phase encodings.
 
 The `.add` method takes a single μs time value and adds it to the last value
 in the array: this pulse lengthening is used in bi-phase encodings.
 
-On completion of the subclass `.tx`, `.transmit` appends a special `STOP` value
-and initiates physical transmission which occurs in an interrupt context.
+On completion of the subclass `.tx`, `.transmit` calls `.trigger` which
+initiates transmission as a background process. Its behaviour is platform
+dependent.
+
+## 4.1 Pyboard
 
 
-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%.
+Tramsmission 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 the 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
 
 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
-and re-initialises T5 for the new duration.
+and re-initialises T5 for the new duration. If it is `STOP` it ensures that the
+duty ratio is set to the `_SPACE`
+
+Here `.trigger` appends a special `STOP` value and initiates physical
+transmission by calling the Timer5 callback.
 
 ## 4.2 ESP32
 
 
 ## 4.2 ESP32
 
-The carrier is output continuously at the specified duty ratio. A pulse train
-generated by the RMT instance drives a hardware gate such that the IR LED is
-lit only when both carrier and RMT are high.
+The RMT class now supports `carrier_freq` and `carrier_duty_percent`
+constructor args, so the base class `IR` (in `__init__.py`) uses these to
+enable the OOK (on-off keying) waveform.
+
+The `.trigger` method calls `RMT.write_pulses` and returns with `RMT` operating
+in the background.
+
+## 4.3 Duty ratio
+
+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.
 
 
-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. Unsupported protocols
 
 
-# 5. References
+You can use the receiver module to capture an IR burst and replay it with the
+transmitter. This enables limited support for unknown protocols. This is
+strictly for experimenters and I haven't documented it in detail.
 
 
-[General information about IR](https://www.sbprojects.net/knowledge/ir/)
+There are two limitations. The first is timing accuracy: both receiving and
+transmitting processes introduce some timing uncertainty. This is only likely
+to be a practical problem with fast protocols. In brief testing with a known
+protocol the scripts below worked.
 
 
-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)
+The more tricky problem is handling repeat keys: different protocols use widely
+varying approaches. If repeat keys are to be supported some experimentation and
+coding is likely to be required.
 
 
-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)
+The following captures a single burst and saves it to a file:  
+```python
+from ir_rx.acquire import test
+import ujson
+
+lst = test()  # May report unsupported or unknown protocol
+with open('burst.py', 'w') as f:
+    ujson.dump(lst, f)
+```
+This replays it:  
+```python
+from ir_tx import Player
+from sys import platform
+import ujson
+
+if platform == 'esp32':
+    from machine import Pin
+    pin = (Pin(23, Pin.OUT, value = 0), Pin(21, Pin.OUT, value = 0))
+else:
+    from pyb import Pin, LED
+    pin = Pin('X1')
+with open('burst.py', 'r') as f:
+    lst = ujson.load(f)
+ir = Player(pin)
+ir.play(lst)
+```
+The `ir_tx.Player` class is a minimal subclass supporting only the `.play`
+method. This takes as an arg an iterable comprising time values of successive
+mark and space periods (in μs).
 
 
-Sony protocol:  
-[SIRC](https://www.sbprojects.net/knowledge/ir/sirc.php)
+The `ir_rx.acquire.test` function makes assumptions about the likely maximum
+length and maximum duration of a burst. In some cases this may require some
+modification e.g. to instantiate `IR_GET` with different args.