peterhinch / micropython_ir

Nonblocking device drivers to receive from IR remotes and for IR "blaster" apps.
MIT License
240 stars 51 forks source link

esp32.RMT module updated, 'TypeError' rise when ir_tx instance initialize. #9

Closed water5 closed 2 years ago

water5 commented 2 years ago

esp32.RMT module already updated, some arguments changed, https://docs.micropython.org/en/latest/library/esp32.html?highlight=rmt#esp32.RMT

When ir_tx instance initialize, rise error below:

from ir_tx.nec import NEC

nec = NEC(Pin(23, Pin.OUT, value = 0))

Traceback (most recent call last): File "stdin", line 1, in module File "/lib/ir_tx/nec.py", line 17, in init File "/lib/ir_tx/init.py", line 45, in init TypeError: extra keyword arguments given

peterhinch commented 2 years ago

I can't replicate this. I suspect you have an outdated file, possibly __init__.py?

MicroPython v1.17 on 2021-09-07; Raspberry Pi Pico with RP2040
Type "help()" for more information.
>>> from machine import Pin
>>> from ir_tx.nec import NEC
>>> nec = NEC(Pin(23, Pin.OUT, value = 0))
>>> 
water5 commented 2 years ago

I try RPi PICO use same version of ir_tx library, https://github.com/peterhinch/micropython_ir/commit/2ebf5db498266618fc0f037469309a6ea1906304, works fine, the __init__.py line 45,

self._rmt = RMT(0, pin=pin, clock_div=80, carrier_freq=cfreq, carrier_duty_percent=duty)  # 1μs resolution

but with ESP32 not, used incorrect parameter.

The latest esp32.RMT module has new parameter:

class esp32.RMT(channel, *, pin=None, clock_div=8, idle_level=False, tx_carrier=None)
water5 commented 2 years ago

Change to

self._rmt = RMT(0, pin=pin, clock_div=80, tx_carrier = (cfreq, duty, 1))

but rise error:

from ir_tx.nec import NEC
nec = NEC(Pin(23, Pin.OUT, value = 0))
nec.transmit(1, 2)

Traceback (most recent call last): File "stdin", line 1, in module File "/lib/ir_tx/init.py", line 90, in transmit File "/lib/ir_tx/init.py", line 98, in trigger TypeError: function doesn't take keyword arguments

peterhinch commented 2 years ago

Oh dear, the maintainers have changed the calling conventions. It may be a little while before I can address this. On the face of it the above error can be fixed by changing lin 98 of __init__.py to read:

self._rmt.write_pulses(tuple(self._mva[0 : self.aptr]))

If you feel like submitting a PR with tested fixes I'll be pleased to merge it :)

water5 commented 2 years ago

I try it. :)

peterhinch commented 2 years ago

I've checked my micropython_remote repo which has been updated to handle the API changes. This has this line so I think the above should work.

water5 commented 2 years ago

What difference between micropython_remote and micropython_ir?

peterhinch commented 2 years ago

The micropython_remote repo is for 433MHz radio controlled remotes. It is very different because there are no published standards for these. You have to use a radio receiver to capture signals from the transmitter so that you can replay them. The receivers are a great way to safely switch mains devices under program control.

Have the changes we discussed worked OK?

bai-yi-bai commented 2 years ago

Hello again, Peter!

I can help with this.

After our recent asyncio documentation adventure, I returned to a project I started 6 months ago with more confidence: building an IR transmitter to augment my monitor's remote control. I intended to create a small box with button 'presets' that would allow me to set the brightness so that I would not have to hold the button down for long periods of time. Why? With each power-on/power-off or input change, the settings are reset.

~5 years ago I had built an IR transmitter project using Arduino to control a duct fan, so I was sure that with micropython I could put this together in no time. However, I put the project in a drawer because I could not get the IR transmit function working correctly with my RPI Pico. At the time, I was successfully able to capture the remote's 24 button codes - it used simple NEC_8 formatted codes, but I was unable to use the ir-tx library to send them. I wasn't sure if I had to send them as hex, or if integers were sufficient since the test.py formats them as hex; maybe aligning these would help future users. I thought about raising an issue, but wanted to make 100% sure that my circuit and code was correct before troubling maintainers.

Today, I built up a simple circuit with a BC547, an IR LED (clear; I'm unsure of the wavelength, but I have several tinted ones which don't work), a 33 Ohm current limiting resistor, an external breadboard 5 V supply, and a 1 K Ohm resistor between the transistor's base and the GPIO pin. Using a cellphone, I was able to observe the flashes of IR light, however the monitor did not respond :(. Also, with my IR receiver powered, I saw the on-board led reacting, but I couldn't be sure if this was just noise. I knew that IR devices can be tricky. On my previous Arduino IR fan project, I remembered that I had captured the raw remote codes because I couldn't get whatever library I was using to work and even then I ended up needing to play each code 3 times for them to be identified by the fan.

I tried setting up this code to loop through with my Pico to see if the monitor would react.

import time
from ir_tx.nec import NEC
nec = NEC(Pin(17, Pin.OUT, value = 0))
dict_of_codes = {
   "Volume Up":25,
### More omitted
    }
addr=96

while True:
    for function in dict_of_codes:
        print(function, addr, dict_of_codes[function])
        nec.transmit(addr=96,data=dict_of_codes[function])
        time.sleep(1)

Unfortunately this didn't work, but I was fairly confident my circuit was correct; I even pulled my old project apart to inspect the transistor package orientation (Youtuber Noel's Retro Lab recently had a video about different vendors changing transistor orientations) and resistor values. Defeated, I decided to go back to the documentation, which led me to... Transmitter Section 5 - Unsupported Protocols. I copied the raw capture code to Thonny, started the script, pressed a button on my remote, and got this output saved to the burst.py file: [8885, 4530, 516, 611, 517, 611, 612, 514, 516, 611, 515, 612, 514, 1749, 511, 1743, 517, 616, 514, 1744, 514, 1744, 516, 1744, 517, 1743, 516, 1742, 517, 611, 515, 612, 515, 1746, 517, 1743, 516, 612, 514, 612, 515, 1744, 516, 1742, 518, 609, 516, 612, 515, 615, 516, 611, 515, 1793, 466, 1745, 515, 612, 515, 610, 568, 1690, 516, 1744, 515, 1745, 516] I then proceeded to attempt playback.

With those changes, the script replied fine. In addition, the script using nec.transmit worked fine too, but only on the ESP32 (with pin 23), however, when I switched back to the RPI Pico, it still did not work.

So for me, water5 and your solutions are working for the ESP32, but I don't know what's wrong with the Pico.

peterhinch commented 2 years ago

Thanks for confirming that the latest code works on ESP32.

I'm not sure what to say about the Pico: in my testing and that of @water5 the Pico works. It's hard to debug these things remotely :)

bai-yi-bai commented 2 years ago

You're welcome, Peter.

Yes, I know debugging remotely can be difficult. However, I'm concerned there is a bug in the transmit code for the RP2 routines. <Should I open a new issue?>

IR tx works on the ESP32, but IR tx on the RP2 doesn't. I tried to set up an experiment which I could repeat four times. The table at the end of this post uses these shorthand names:

  1. ESP-RM = rx ESP32 from tx remote
  2. ESP-RP2 = rx ESP32 from tx RP2 "MicroPython v1.17 on 2021-09-02; Raspberry Pi Pico with RP2040"
  3. RP2-RM = rx RP2 from tx remote
  4. RP2-ESP = rx RP2 from tx ESP32 "MicroPython v1.17 on 2021-09-02; ESP32 module with ESP32"

tx code (manually entered into the REPL console using Thonny)

from machine import Pin
from ir_tx.nec import NEC
nec = NEC(Pin(16, Pin.OUT, value = 0)) # Pin 16 on RPI2, Pin 23 on ESP32
nec.transmit(addr=96,data=93)

I created four capture_raw python files - two for the RP2 and two for the ESP32 results. This was useful when comparing console output and consolidating the information. I ran these using Thonny's "Run current script" option.

capture_raw_ir_code_xxx_from_yyy.py contents

from ir_rx.acquire import test
import ujson
lst = test()  # May report unsupported or unknown protocol
with open('XXXX_from_YYYY.py', 'w') as f:
    ujson.dump(lst, f)

The output files were:

  1. esp32_from_remote.py
  2. esp32_from_rp2.py
  3. rp2_from_remote.py
  4. rp2_from_esp32.py

Results

Legend: Column 1 is the event Column 2 is the ESP32 capture from the remote Column 3 is the ESP32 capture from the RP2 (nec.transmit) Column 4 is the RP2 capture from the remote Column 5 is the RP2 capture from the ESP32 (nec.transmit)

TIME ESP-RM ESP-RP2  RP2-RM RP2-ESP
---     NEC    Unkn     NEC     NEC
000    8938    8988    9028    9010
001    4508    4363    4453    4414
002     571     629     568     630
003     552     561     555     500
004     574     550     563     632
005     554     515     561     470
006     603     626     570     662
007     520     508     559     490
008     576     633     566     603
009     553     513     558     507
010     573    2205     518     544
011     553    1712     618     608
012     620     231     517     571
013    1735     209    1735    1669
014     476     286     570     593
015    1691     985    1684    1646
016     567     627     577     592
017     564     557     558     556
018     566     555     568     584
019    1688    1659    1689    1659
020     576     625     570     599
021    1683    1628    1700    1644
022     576     662     562     625
023    1769    1609    1687    1630
024     487     590     570     596
025    1691    1649    1723    1650
026     567     607     537     626
027    1692    1644    1695    1614
028     696     611     570     649
029     428     507     555     473
030     575     634     567     583
031     551     509     558     549
032     574     633     572     590
033    1687    1617    1695    1653
034     576     638     566     619
035    1681    1623    1693    1646
036     580     632     565     586
037     551     526     562     513
038     574     589     568     616
039    1685    1644    1685    1675
040     695     612     573     585
041    1560    1667    1688    1671
042     580     588     571     609
043    1683    1644    1691    1611
044     575     612     564     614
045     552     715     565     513
046     571     399     566     627
047    1693    7325    1691    1610
048     567    2525     524     657
049     564    1371     608     481
050     565     309     518     609
051     560     488     607     514
052     684    1121     521     522
053    1571     229    1740    1713
054     576     166     517     664
055     550    1058     610     475
056     575     308     515     661
057     553     176     609     470
058     574    1075     518     615
059     552    1259     607     511
060     571    2153     521     594
061    1692     162    1748    1642
062     567     231     513     659
063     559     END     602     492
064     567     END     524     606
065    1688     END    1740    1638
066     576     END     522     633

END = Did not exist

Lengths: Col 2 esp-rm 68201 Col 3 esp-rp2 72282 Col 4 rp2-rm 68188 Col 5 rp2-esp 68072

ESP-RP2 (Column 3)

Something is really wrong here with this nec.transmit() function:

After reviewing the Altium - NEC Infrared Transmission Protocol page, I can see:

That's as far as I can go without diving deeper into how the transmit function works.

peterhinch commented 2 years ago

I can't replicate this.

I have verified that the bit patterns in your columns labelled NEC are correct for the values of address and data you are using. However with a logic analyser on pin 16 of a Pico, and pasting your exact transmit code I see the same correct bit pattern:

from machine import Pin
from ir_tx.nec import NEC
nec = NEC(Pin(16, Pin.OUT, value = 0)) # Pin 16 on RPI2, Pin 23 on ESP32
nec.transmit(addr=96,data=93)

This is the start of the waveform (note that what we are seeing here are bursts of carrier): Image and here are the data bits, the trace starting with the trailing end of the 4.5ms off start state: Image I suspect you have some kind of communication problem between transmitter and receiver, but it's impossible for me to check this remotely.

bai-yi-bai commented 2 years ago

Thank you, Peter.

I tried to rule out user error and re-read the documentation from scratch. I cannot find any setting, or configuration which made any difference. I even tried v1.18 (2022-01-17). I did discover that my ESP32S will power the IR LED without the transistor circuit. I'm not sure about the RP2, I see it flash on camera, but the signal doesn't register (as usual). While I do have an air conditioning unit in the same room which periodically syncs temperature data with the remote, it shouldn't cause shorter timing coming out of the RP2.

This is really quite baffling. For months I thought this was my own user error, but seeing it work on the ESP32S makes me doubt it. I checked with the verbose class flag, and the data being appended is identical across both devices (as expected). I have an old habit of going through code and inserting print() statements to better understand what's happening, but everything looks the same, except for the timeit() function takes longer on the ESP32, which might be due to the older architecture.

The only difference I see is that the ESP32 uses RMT, whereas the RP2 uses RP2_RMT. Of course, the underlying architectures are likely totally different. I really didn't plan on learning how the RPI_RMT module and PIO worked, but maybe that's my next step to see if I can alter the timing output. This was touted as one of the main selling points of the RP2040.

This is a long shot, but which silicon stepping of RP2040 do you have? I have two RP2040B1 devices on my RPI Picos. Other than a) an undiscovered silicon issue, b) different onboard crystals impacting silicon operation, c) an existing errata impacting this (maybe the watchdog timer one noted in the datasheet), d) some other production-related variable which we cannot determine.

Just for reference, here is one of my device's unique ID: machine.unique_id() b'\xe6\xa4\x93\x17Xj&'` I don't know if this number is incremented as units are produced, but I do find it amusing that the first 2 bytes are 椓 = beat... this thing sure is beating me up. I might try go picking up some RPI Picos from a different vendor to see if that will make a difference.

I really don't see what else it could be. Maybe not many people have used Picos for IR projects? I found 0 examples on YouTube of IR transmit functions and even fewer results on google.

This might finally be an excuse to buy an oscilloscope. I saw the YouTuber Adrian's Digital Basement Demo a 60 USD Unit recently.

peterhinch commented 2 years ago

which silicon stepping of RP2040 do you have?

How can I determine this?

I think it's unlikely that it's that sort of problem. My usage of the PIO is straightforward. I've found it entirely reliable and I've heard no complaints about it on the forum. I would investigate your electronics first.

An oscilloscope would make this a lot easier, preferably a digital storage scope. In your position I would examine the waveform at the pin, with the pin unconnected, as I did. Assuming it's correct, then add your electronics and check again. I would definitely not drive the LED without a transistor or MOSFET buffer (on any platform). Note that my suggested designs use voltages higher than 3.3V: this is to ensure adequate drive for the LED. The fact that you can detect a flash says nothing about whether it is getting a clean pulse.

bai-yi-bai commented 2 years ago

which silicon stepping of RP2040 do you have?

How can I determine this? I used my phone to take a picture of the RP2040 package on the board. With just the correct angle I was able to capture the laser engraving.

I'll see if I can capture the output with an oscilloscope in the next few days.