Page 1 of 3

IR receiver/transmitter for ESP8266?

Posted: Wed Dec 28, 2016 2:46 am
by askvictor
It seems the ESP8266 has some hardware support for infrared remote control - see chapter 13 of http://www.espressif.com/sites/default/ ... e_en_0.pdf

Has anyone had a go at exposing this in micropython?

Re: IR receiver/transmitter for ESP8266?

Posted: Thu Dec 29, 2016 9:02 pm
by mcauser
Interesting :)

For comparison, here's an Arduino flavoured, software only approach: https://github.com/esp8266/Basic/tree/m ... oteESP8266

Re: IR receiver/transmitter for ESP8266?

Posted: Fri Dec 30, 2016 9:07 am
by pythoncoder
For IR reception there are demodulator chips e.g. Vishay TSOP4838 http://cpc.farnell.com/vishay/tsop4838/ ... dp/SC09303. These cost peanuts. They save having to deal with the 38KHz carrier, which presents a number of technical issues including rejecting optical interference signals. The chip gives you a relatively slow pulse waveform which you can decode.

I have some Arduino C code for doing this with the fairly common NEC protocol if anyone's interested - it should be reasonably easy to port to MicroPython.

Re: IR receiver/transmitter for ESP8266?

Posted: Thu Mar 09, 2017 6:24 am
by mmh
I spent a bit of time getting a basic NEC IR receiver working unfortunately its sensitive to time so if a GC goes off the timing is not met and you get a decode error. This describes the setup minus the UNO + ESP12 board. https://learn.sparkfun.com/tutorials/ir-communication

https://github.com/mhoffma/esp/blob/master/ir.py

I added a pin output to the isr routine to show how much time is being consumed capturing the timestamps.

[img][(https://github.com/mhoffma/esp/blob/master/irpy.png)]

To run this with the debug pin enabled you need to set the machine frequency to 160Mhz otherwise you will never see a successful decode. The polarity calculation also causes problems in that it adds a lot of extra latency to the tiny isr.

Re: IR receiver/transmitter for ESP8266?

Posted: Thu Mar 09, 2017 9:14 am
by Roberthh
Can't you use machine.time_pulse_us(pin, pulse_level, timeout_us=1000000) for your purpose?
B.T.W.: Lines 45 .. 47 could be written as:

Code: Select all

self.wptr = (self.wptr + 1) % self.BUFSZ
You may also try the native code decorator @micropython.native for speed improvement. That typically gains a factor of two. Sometimes it works.

Re: IR receiver/transmitter for ESP8266?

Posted: Thu Mar 09, 2017 5:26 pm
by mmh
I started with that time_pulse_us, I didn't have a lot of success, the thing is so sensitive did you take a peak at the time line I posted a picture of it, the bottom trace is how much time I'm spending in the ISR its huge compared to the size of those pulse widths we are trying to recognize.

Thanks for the @micropython.native tip, I didn't know that existed does that assemble the python code into native assembler or something cool like that?

Re: IR receiver/transmitter for ESP8266?

Posted: Thu Mar 09, 2017 5:46 pm
by pythoncoder
See these references for explanations of the different code emitters:
https://www.kickstarter.com/projects/21 ... sts/664832
https://www.kickstarter.com/projects/21 ... sts/665145

You might also be interested in this https://github.com/peterhinch/micropyth ... aremote.py which is a decoder for NEC IR remotes using asyncio. This was tested on the Pyboard, so may have issues on the ESP8266.

Re: IR receiver/transmitter for ESP8266?

Posted: Thu Mar 09, 2017 7:24 pm
by mmh
Peter how is the performance of your implementation? Does your code suffer from latency issues like mine does, e.g. if I add a Timer() to my system my codes robustness drops do you have a feeling about your nice implementation which I will start working with ASAP.

This is exactly what I want to learn next!

async def _run(self):
loop = asyncio.get_event_loop()
block_time = self.block_time
while True:
await self._ev_start # Wait unitl data collection has started
delta = block_time - ticks_diff(loop.time(), self._ev_start.value())
await asyncio.sleep_ms(delta) # Data block should have ended
self._decode() # decode, clear event, prepare for new rx, call cb


I really like that native decorator, that is so nice. Are you allowed to imported functions my routine which is a callback for a pin seems to fail on missing symbol tick_us when I add the @native it.

Code: Select all

    @micropython.native                                                                                                                                                                       
    def timeseq_isr(self,p):                                                                                                                                                                  
        '''compute edge to edge transition time, ignore polarity'''                                                                                                                           
        e=ticks_us()                                                                                                                                                                          
        self.dbgport.high()                                                                                                                                                                   
        #s=2*p.value()-1                                                                                                                                                                      
        d=ticks_diff(e,self.ledge)                                                                                                                                                            
        self.ledge=e                                                                                                                                                                          
        self.buf[self.wptr]=d #*s                                                                                                                                                             
        self.wptr=(self.wptr+1)&self.BUFSZ                                                                                                                                                    
        self.dbgport.low()                                                                                                                                                                    

Code: Select all

>>>
>>> NameError: name 'ticks_us' is not defined
NameError: name 'ticks_us' is not defined
NameError: name 'ticks_us' is not defined
NameError: name 'ticks_us' is not defined
NameError: name 'ticks_us' is not defined
NameError: name 'ticks_us' is not defined
NameError: name 'ticks_us' is not defined
NameError: name 'ticks_us' is not defined

which all lead to rebbot.

Re: IR receiver/transmitter for ESP8266?

Posted: Sat Mar 11, 2017 2:23 am
by mmh
if I rewrite my isr this way it work much better and the performance is so much better thanks for the tip:

Code: Select all

  def __init__(self...)
  ....
       # cache ticks functions for native call                                                                                                                                               
        self.ticks_diff = time.ticks_diff                                                                                                                                                     
        self.ticks_us = time.ticks_us                                                                                                                                                         
  ...
    @micropython.native                                                                                                                                                                       
    def timeseq_isr(self,p):                                                                                                                                                                  
        '''compute edge to edge transition time, ignore polarity'''                                                                                                                           
        e=self.ticks_us()                                                                                                                                                                     
        self.dbgport.high()                                                                                                                                                                   
        s=2*p.value()-1                                                                                                                                                                       
        d=self.ticks_diff(e,self.ledge)                                                                                                                                                       
        self.ledge=e                                                                                                                                                                          
        self.buf[self.wptr]=d*s                                                                                                                                                               
        self.wptr=(self.wptr+1)&self.MASK                                                                                                                                                     
        self.dbgport.low()                                                                                                                                                                    

Re: IR receiver/transmitter for ESP8266?

Posted: Mon Mar 13, 2017 3:08 pm
by Roberthh
@mmh: Thank you for sharing your experience. It helped my to overcome the error I ran into when trying to run a ISR as native code. It did not resolve the error at that time, but happily you did. I made the attempt to transform your example into viper code, which ought to be faster. I succeeded, but the performance gain is minor, like 10%, and therefore maybe not worth the reduced flexibility in the code, unless you are fighting for the last few clock cycles. And you might find direct access to the GPIO pins interesting, which is very fast (~75 ns per access).
The script skeleton below arbitrary selected GPIO bits. If you want to try it yourself, you have to adapt the read and write to the GPIO_BASE array. The GPIO number follow the bit numbers in that word. Unfortunately there is not simple way in viper to store an integer into a python object. So I had to use a two element array for the buffer index and e. The sample function takes about 280 µs @ 80 MHz and 180µs @ 160 MHz, compared to 318 µs @ 80 MHz using native code. The empty function call takes ~100 µs @ 80MHz.

Code: Select all

import machine
import time
import array

_BUFFERSIZE = const(32) # Ringbuffer Size
_BUFFERMASK = const(0x1f)

BURST = 40

class acquire:

    def __init__(self):
        self.ticks_us = time.ticks_us
        self.ticks_diff = time.ticks_diff
        self.data = array.array("L", [0] * _BUFFERSIZE)
        self.index_ledge = array.array("L", [0] * 2)

    @micropython.viper
    def sample(self, x):
        e = int(self.ticks_us())
        GPIO_BASE = ptr32(0x60000300) # GPIO Base register
        GPIO_BASE[1] = 0x10 # set bit 4 = GPIO4
        s = ((GPIO_BASE[6] & 0x20) >> 4) - 1 # read GPIO 5
        d = int(self.ticks_diff(e, self.index_ledge[1]))
        index = int(self.index_ledge[0])
        self.data[index] = d * s
        index = (index + 1) & _BUFFERMASK
        self.index_ledge[0] = index
        self.index_ledge[1] = e
        GPIO_BASE[2] = 0x10 # clear bit 4 = GPIO4
#