X-Git-Url: https://vault307.fbx.one/gitweb/micorpython_ir.git/blobdiff_plain/36140aa568d4a538adafb03e575b26de8a0784ef..8450d109e166b999af515ec5f038db794b6e090b:/TRANSMITTER.md?ds=inline diff --git a/TRANSMITTER.md b/TRANSMITTER.md index 3478ba8..a328d68 100644 --- a/TRANSMITTER.md +++ b/TRANSMITTER.md @@ -5,7 +5,7 @@ # 1. Hardware Requirements 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 +Output is via an IR LED which needs a simple circuit to provide sufficient current. Typically these need 50-100mA of drive to achieve reasonable range and data integrity. A suitable 940nm LED is [this one](https://www.adafruit.com/product/387). @@ -13,10 +13,16 @@ 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. -On ESP32 pin 23 is used for IR output and pins 18 and 19 for pushbuttons. The -ESP32 solution has limitations discussed in [section 5.2](./TRANSMITTER.md#52-esp32). +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 Wiring +On Raspberry Pi Pico the demo uses pin 17 for IR output and pins 18 and 19 for +pushbuttons. These pins may be changed. The driver uses the PIO to emulate a +device similar to the ESP32 RMT. The device driver is +[documented here](./RP2_RMT.md); this is for experimenters and those wanting to +use the library in conjunction with their own PIO assembler code. + +## 1.1 Pyboard Wiring I use the following circuit which delivers just under 40mA to the diode. R2 may be reduced for higher current. @@ -30,23 +36,55 @@ increase power. 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`. -# 2. Installation +## 1.2 ESP32 Wiring -The transmitter is a Python package. This minimises RAM usage: applications -only import the device driver for the protocol in use. +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. -Copy the following to the target filesystem: - 1. `ir_tx` Directory and contents. +## 1.3 RP2 Wiring + +There is no `active_high` option so the circuit must illuminate the LED if the +pin state is high, as per the above drivers. Test programs use pin 17, but this +can be reassigned. + +# 2. Dependencies and installation + +## 2.1 Dependencies The device driver has no dependencies. -The demo program requires `uasyncio` from the official library and `aswitch.py` -from [this repo](https://github.com/peterhinch/micropython-async). The demo is -of a 2-button remote controller with auto-repeat. It may be run by issuing: +On ESP32 a firmware version >= V1.17 is required. The Loboris port is not +supported owing to the need for the RMT device and other issues. + +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 +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. +The demo is of a 2-button remote controller with auto-repeat. It may be run by +issuing: ```python from ir_tx.test import test ``` @@ -54,38 +92,90 @@ Instructions will be displayed at the REPL. # 3. The driver -This is specific to Pyboard D, Pyboard 1.x (not Lite) and ESP32. +This is specific to Pyboard D, Pyboard 1.x (not Lite), ESP32 and Raspberry Pi +Pico (RP2 architecture chip). It implements a class for each supported protocol, namely `NEC`, `SONY_12`, `SONY_15`, `SONY_20`, `RC5` and `RC6_M0`. Each class is subclassed from a common abstract base class in `__init__.py`. The application instantiates the appropriate class and calls the `transmit` method to send data. -The ESP32 platform is marginal in this application because of imprecision in -its timing. The Philips protocols are unsupported as they require unachievable -levels of precision. Test results are discussed [here](./TRANSMITTER.md#52-esp32). +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 +``` +Basic usage on Pico: +```python +from machine import Pin +from ir_tx.nec import NEC +nec = NEC(Pin(17, Pin.OUT, value = 0)) +nec.transmit(1, 2) # address == 1, data == 2 +``` + +#### Common to all classes -Constructor -All constructors take the following args: - 1. `pin` An initialised `pyb.Pin` instance supporting Timer 2 channel 1: `X1` - is employed by the test script. Must be connected to the IR diode as described - below. +Constructor args: + 1. `pin` A Pin instance instantiated as an output. On a Pyboard this is a + `pyb.Pin` instance supporting Timer 2 channel 1: `X1` is employed by the test + script. On ESP32 any `machine.Pin` may be used. Must be connected to the IR + diode as described below. 2. `freq=default` The carrier frequency in Hz. The default for NEC is 38000, Sony is 40000 and Philips is 36000. - 3. `verbose=False` If `True` emits debug output. + 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 -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 +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. @@ -93,12 +183,17 @@ The NEC protocol accepts 8 or 16 bit addresses. In the former case, a 16 bit value is transmitted comprising the 8 bit address and its one's complement, enabling the receiver to perform a simple error check. The `NEC` class supports these modes by checking the value of `addr` passed to `.transmit` and sending -the complement for 8 bit values. +the complement for values < 256. -`toggle` is ignored. +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` @@ -110,6 +205,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. @@ -119,75 +219,134 @@ 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. -# 4. Test results +#### 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. -# 5. Principle of operation +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. -## 5.1 Pyboard +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 `transmit` method calls a `tx` method of the subclass which populates this array. This is done by two methods of the base class, `.append` and `.add`. The -former takes a list of times (in μs) and appends them to the array. A bound +former takes a list of times (in ) and appends them to the array. A bound variable `.carrier` keeps track of the notional on/off state of the carrier: 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. -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 -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` -## 5.2 ESP32 +Here `.trigger` appends a special `STOP` value and initiates physical +transmission by calling the Timer5 callback. -This is something of a hack because my drivers work with standard firmware. +## 4.2 ESP32 -A much better solution will be possible when the `esp32.RMT` class supports the -`carrier` option. A fork supporting this is -[here](https://github.com/mattytrentini/micropython). You may want to adapt the -base class to use this fork: it should be easy and would produce a solution -capable of handling all protocols. +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. -A consequence of this hack is that timing is imprecise. In testing NEC -protocols were reliable. Sony delivered some erroneous bitsreams but may be -usable. Philips protocols require timing precision which is unachievable; these -are unsupported and an exception will be thrown on an attempt to instantiate a -Philips class on an ESP32. +The `.trigger` method calls `RMT.write_pulses` and returns with `RMT` operating +in the background. -The ABC stores durations in Hz rather than in μs. This is because the `period` -arg of `Timer.init` expects an integer number of ms. Passing a `freq` value -enables slightly higher resolution timing. In practice timing lacks precision -with the code having a hack which subtracts a nominal amount from each value to -compensate for the typical level of overrun. +## 4.3 Duty ratio -The carrier is generated by PWM instance `.pwm` with its duty cycle controlled -by software timer `._tim` in a similar way to the Pyboard Timer 5 described -above. The ESP32 duty value is in range 0-1023 as against 0-100 on the Pyboard. +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. -# 6. References +# 5. Unsupported protocols -[General information about IR](https://www.sbprojects.net/knowledge/ir/) +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. -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) +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. -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 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. + +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.