https://toitlang.org/ logo
RMT IR
# help
c
Hi all, very new to Toit - but loving it so far. I'm having a go at building a primitive 'laser tag' game using a few ESP32S3s, IR LEDs and IR receivers. The idea is a player has a 'laser' that beams its ID when a trigger is pressed, and the IR data is picked up by the other player's receiver. I've started digging around in the RMT documentation, but I'm in over my head. Are there any code snippets around that I could use to get started (i.e. send an IR code, receive an IR code)? Thanks
f
There isn't any library that uses the RMT for the intended purpose (IR codes of remote controls), but I don't think that's really needed. Fundamentally, you configure the RMT to send a certain bit-pattern, optionally with a carrier frequency. On the other side, you ask the receiver to detect these patterns. So far, I have only used the RMT for wired peripherals (like the HC-SR04, the DHT11, or the one-wire bus) where the input is always "clean". I think it should work with noisy infra-red too, though.
Unfortunately, I don't have any IR receiver, so I can't try to get an example working.
c
@floitsch OK thanks - I'll see if I can re-purpose some code that makes use of the wired hardware you mentioned.
f
Don't hesitate to reach out to me if you have questions about the RMT peripheral.
c
@floitsch I got the transmit part working (well at least I have it pulsing a non-IR led!). But I'm getting an error creating a Rx channel. Here's my test code:
Copy code
pin_ := gpio.Pin.in 18
  rx := rmt.Channel pin_ --input
        --idle_threshold=26_000
        --filter_ticks_threshold=10
        --clk_div=233
And the error I'm getting is:
Copy code
E (14590) rmt(legacy): rmt_set_gpio(528): RMT CHANNEL ERR
E (14590) rmt(legacy): rmt_config(673): set gpio for RMT driver failed
----
EXCEPTION error. 
UNKNOWN ERROR 0x102(258)
  0: rmt-config-rx_            <sdk>/rmt.toit:574:3
  1: Channel.configure         <sdk>/rmt.toit:381:5
  2: Channel                   <sdk>/rmt.toit:294:12
  3: main                      test-ir.toit:10:9
I've tried using a few different GPIO pins, but get the same error. device has version esp32s3-20210327 Build:Mar 27 2021 Appreciate any suggestions
f
I think it's unrelated, but try to
pin_ := gpio.Pin 18
(without the
.in
). The RMT will configure it correctly itself.
I will try to have a look once I'm at work.
c
Same error; Thanks for the speedy reply
f
In the meantime: could you maybe try to run the example code for the hc-sr04 package: https://pkg.toit.io/package/github.com%2Flask%2Ftoit-hc-sr04@v2.1.1
It uses the RMT and should start fine without anything connected.
c
Will do
f
I can reproduce. It's an issue with the esp32s3. The same code works fine on the esp32. I will try to fix it.
c
Much appreciated. I just tested on a new board (S3) too. Not sure if it's relevant, but I use
jag flash --chip esp32s3-spiram-octo
for these boards.
f
Thanks. Since I can reproduce on the standard s3 devkit, that shouldn't matter. Probably some initialization code that is different on the ESP32S3 compared to the ESP32.
c
Let me know if you'd like me to create an issue in github, or if I can help in any way.
f
The reason for the error is that the ESP32S3 uses the first half (channel 0-3) for transmitting and the second half (4-7) for receiving. It's just a small line in the documentation and we overlooked it. I will change the code to allocate the correct channel when initializing the RMT.
c
Great. So, how might one get hold of your fix when its ready?
f
It will be in the next release.
In the meantime, you can give a channel-id as argument. If you use 4+ then you should get an rx channel.
c
Excellent - I'll give it a try
f
The RMT fix has been reviewed and is now in the commit queue. It will be part of the next release (which should happen within the next few days).
c
@floitsch do you think this ESP-IDF example could be a good starting point for a Toit IR package? I can see echos of Signals and Channels in there.. https://github.com/husarnet/esp-idf/blob/master/examples/peripherals/rmt_nec_tx_rx/main/infrared_nec_main.c
f
Afaik, NEC is the standard in IR remote controls. The example looks reasonable, and it should be possible to port it to Toit. Having an ir-NEC package would be great. I would start by trying to get the example working, but once that's done there is nothing in the way of making it a package. Maybe even with a way to get a database of common IR codes.
c
@floitsch I've been doing some tinkering with RMT and IR; Transmitting random signals is easy enough, and I can receive IR pulses too (
signals.stringify
shows a bunch of numbers when I press IR remote button). But I'm struggling with a few things, maybe you can give me some pointers: 1. For transmitting, how can I represent the NEC message structure using signals? For example, it uses a 9ms burst as a header. How do I map 9ms to a signal period (i.e. ticks)? 2. And.. should I use a single
rmt.Signals
instance to represent the entire message (header + address + command + eom), or should the message be made and transmitted as a series of Signals instances? 3. NEC IR uses a carrier frequency of 38kHz. I assume this means I'll need to use the
--carrier-frequency-hz
option. What other options might I need?
--clk-div
and
--carrier-duty-percent
? 4. When it comes to reading in
Signals
- can you let me know a rough outline of how to parse these into usable data? I'm assuming that I would first match a signal pulse to the 9ms header, and then buffer/parse the remaining pulses (address + command, etc) until I detect the EOM pulse? I've been trying to repurpose the 1wire source code, but I'm struggling to see the big picture of how it all hangs together. Anyways - any tips appreciated.
f
Fyi, we are still using the old API of the RMT, which ESP-IDF has here: https://docs.espressif.com/projects/esp-idf/en/v4.4.7/esp32/api-reference/peripherals/rmt.html 1. The RMT peripheral normally runs with the APB clock which is at 80MHz. That's way too fast for most use cases, and we thus normally divide the clock by a constant factor. Dividing it by 80 would give you a 1MHz resolution. With that you could get a 9ms by just setting the length to 9000 ticks. However, since NEC pulse bursts are 562.5us you will want something slightly different. If you divide the 80MHz clock by 45, then it has a resolution of 0.5625us (if I'm not mistaken). So that's probably the most convenient here. Each pulse would then be 1000 ticks. Note that the divider must be in range 1 to 255. So you can't just go to ms resolution. Given that the length can go up to 16 bits, you should still be fine. 2. Depends. If there aren't any timing constraints between header, address, ..., then you could send them separatedly. I would expect that you must send the address at a very specific timing after the header. The main reason not to put it into one signals instance is that you don't have the resolution (like needing to wait for more than 65ms), or the space (only a limited amount of entries can be in a signals instance). Otherwise I would put everything together. 3. Yes. The carrier-frequency should be set to 38000. The duty-percent to 50. (At least I didn't see anything contrary to this).
4. the fun part 🙂 - for development, the easiest is to connect your sending and receiving pins. This way you don't need to deal with bad IR reception,... That can wait until the code is working. - you need to undo the carrier frequency. I think you can do that by just setting a ticks-threshold. That just says that anything that is shorter than that value is ignored. If you set it to something slightly higher than the carrier, then the carrier should be filtered out. - make sure that the idle time is big enough so that you get the whole signal, but not much longer. - once you get data in, print it and have a look to see if it sounds reasonable. You should basically be able to recognize the data that you sent out. - the easiest for parsing is to just run through the individual entries and check whether they make sense. "Start-pulse must be in range 55000-65000us., then we should have xyz, ...".
I found a project that uses the ESP RMT to implement NEC. No idea if it actually works, but it looks ok. https://github.com/husarnet/esp-idf/blob/master/examples/peripherals/rmt_nec_tx_rx/main/infrared_nec_main.c
For example it has:
Copy code
c
static void nec_tx_init()
{
    rmt_config_t rmt_tx;
    rmt_tx.channel = RMT_TX_CHANNEL;
    rmt_tx.gpio_num = RMT_TX_GPIO_NUM;
    rmt_tx.mem_block_num = 1;
    rmt_tx.clk_div = RMT_CLK_DIV;
    rmt_tx.tx_config.loop_en = false;
    rmt_tx.tx_config.carrier_duty_percent = 50;
    rmt_tx.tx_config.carrier_freq_hz = 38000;
    rmt_tx.tx_config.carrier_level = 1;
    rmt_tx.tx_config.carrier_en = RMT_TX_CARRIER_EN;
    rmt_tx.tx_config.idle_level = 0;
    rmt_tx.tx_config.idle_output_en = true;
    rmt_tx.rmt_mode = 0;
    rmt_config(&rmt_tx);
    rmt_driver_install(rmt_tx.channel, 0, 0);
}
The code uses a CLK_DIV of 100, but, as I said earlier, 45 (or 90) feels more advantageous.
c
Thanks for that - I think I've made some progress: * I connected the TX and RX together on the same board. * I built up a single signals instance, and loaded it up with items according to the spec (I think). * The TX signals look good (I think) * I switched off the carrier signal to keep things simple. * The RX signals are only 2-3 items long. The timing on the first signal item is almost always correct:
Copy code
SEND: 121 178
TX: 17 items -- 1-16 0-8 0-1 0-1 1-3 1-3 1-3 1-3 0-0 0-0 1-3 1-3 1-1 1-1 1-1 1-1 0-0 1-1
RX: Reading...
RX: 4 items - 1-16 0-10 1-12 0-0
SEND: 121 178
TX: 17 items -- 1-16 0-8 0-1 0-1 1-3 1-3 1-3 1-3 0-0 0-0 1-3 1-3 1-1 1-1 1-1 1-1 0-0 1-1
RX: Reading...
RX: 4 items - 1-16 0-10 1-12 0-0
I used these Channel settings:
Copy code
CLK-DIV := 45
IDLE-LEVEL := 0
IDLE-THRESHOLD-TICKS := 15000
FILTER-TICKS-THRESHOLD := 5
I tried a range of values for idle threshold and filter ticks, but they don't appear to have much affect on the receiver. My test code essentially calls
rx.start-reading
followed by a short sleep, then
tx.send signals
, then
rx.read
. I feel as though I'm close, but missing something fundamental..
f
You are sending extremely short signals (16, 8, 1, 3 ...). Since your clock is divided by 45, each tick is 0.5625us. However, NEC requires 562.5us. So you are a factor 1000 too fast. Your short pulses are then filtered out by the filter ticks threshold which ignores everything that is shorter than 5 ticks. You will need to use the threshold when you later add a carrier frequency. A carrier frequency of 38khz will add pulses of 1/(2*38000) = 13.15us (I think). In ticks that will be 13.15/.5625=~23. So your threshold will need to be at least 24. Since your shortest signal will be 1000 ticks you could set it a bit higher, like 100 or so.
c
Hmm - so would I use a divider of 23 instead?
f
No. The divider is fine.
c
ah
f
With the carrier frequency on, you will need to change
FILTER-TICKS-THRESHOLD 5
c
each pulse should be 1000 ticks
f
Correct
c
Hot dog!
Copy code
TX: 68 items -- 1-16000 0-8000 1-1000 0-3000 1-1000 0-1000 1-1000 0-3000 1-1000 0-1000 1-1000 0-3000 1-1000 0-3000 1-1000 0-3000 1-1000 0-3000 1-1000 0-1000 1-1000 0-3000 1-1000 0-1000 1-1000 0-3000 1-1000 0-3000 1-1000 0-3000 1-1000 0-3000 1-1000 0-3000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-3000 1-1000 0-3000 1-1000 0-3000 1-1000 0-3000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-0
RX: Reading...
RX: 68 items - 1-16000 0-8000 1-1000 0-3000 1-1000 0-1000 1-1000 0-3000 1-1000 0-1000 1-1000 0-3000 1-1000 0-3000 1-1000 0-3000 1-1000 0-3000 1-1000 0-1000 1-1000 0-3000 1-1000 0-1000 1-1000 0-3000 1-1000 0-3000 1-1000 0-3000 1-1000 0-3000 1-1000 0-3000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-3000 1-1000 0-3000 1-1000 0-3000 1-1000 0-3000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-1000 1-1000 0-0
k
🥳
k
Impress your friends by turning their TV off with an ESP32! 😁
f
Nice. Were you able to test it with a real device (TV...)?
c
No.. I have a Sony TV, I'm assuming they use a different protocol.
f
I wouldn't be surprised if they use NEC. The nec database has some entries for Sony: https://github.com/probonopd/irdb/tree/master/codes/Sony/TV
It might be a slight variant, though. Here is a document that could be relevant: https://faculty-web.msoe.edu/johnsontimoj/Common/FILES/sony_sirc_protocol.pdf
Also: you could try to capture the output of the Sony remote. If you have an IR sensor, you could use the receiver part of the package to see what the remote actually sends.
c
Excellent - I'll look into it
3 Views