Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Support for IR remote control #17906

Closed
dp1 opened this issue Apr 7, 2022 · 3 comments
Closed

[RFC] Support for IR remote control #17906

dp1 opened this issue Apr 7, 2022 · 3 comments

Comments

@dp1
Copy link
Contributor

dp1 commented Apr 7, 2022

Description

As part of a university project I integrated this IR remote and it may be worthwhile to merge this into RIOT. The protocol it uses is NEC, but the IR receiver chip ( HS0038B ) inverts the signal

Would there be any interest in a PR to merge this functionality? I'm attaching the (currently working) code below to give an idea of what I've done so far

ir_remote.h

#ifndef _IR_REMOTE_H
#define _IR_REMOTE_H

#include "periph/gpio.h"
#include "isrpipe.h"

#define IR_REMOTE_START_US 4250
#define IR_REMOTE_ZERO_US 450
#define IR_REMOTE_ONE_US 1550
#define IR_REMOTE_EPS_US 200

typedef struct {
    gpio_t pin;
    isrpipe_t isrpipe;

    uint32_t last_rising;
    uint32_t data;
    int read_bits;
    bool started;

    uint8_t isrpipe_buf[64];
} ir_remote_t;

typedef struct {
    uint8_t addr;
    uint8_t cmd;
} ir_remote_cmd_t;

int ir_remote_read(ir_remote_t *ir, ir_remote_cmd_t *command);
void ir_remote_init(ir_remote_t *ir, gpio_t pin);

#endif

ir_remote.c

#include <stdlib.h>
#include "xtimer.h"

#include "ir_remote.h"

static void ir_remote_isr(void *arg)
{
    ir_remote_t *ir = (ir_remote_t *)arg;

    if(gpio_read(ir->pin) != 0) // rising edge
    {
        ir->last_rising = xtimer_now_usec();
    }
    else // falling edge
    {
        uint32_t length = xtimer_now_usec() - ir->last_rising;

        if(abs(length - IR_REMOTE_START_US) <= IR_REMOTE_EPS_US)
        {
            ir->started = true;
            ir->data = 0;
            ir->read_bits = 0;
        }
        else if(abs(length - IR_REMOTE_ZERO_US) <= IR_REMOTE_EPS_US)
        {
            ir->data = (ir->data << 1);
            ir->read_bits++;
        }
        else if(abs(length - IR_REMOTE_ONE_US) <= IR_REMOTE_EPS_US)
        {
            ir->data = (ir->data << 1) | 1;
            ir->read_bits++;
        }
        else // Interpacket delay
        {
            ir->started = false;
        }

        if(ir->started && ir->read_bits == 32)
        {
            ir->started = false;

            uint8_t addr = (ir->data >> 24) & 0xff;
            uint8_t addr_inv = (ir->data >> 16) & 0xff;
            uint8_t cmd = (ir->data >> 8) & 0xff;
            uint8_t cmd_inv = ir->data & 0xff;

            if((addr ^ addr_inv) == 0xff && (cmd ^ cmd_inv) == 0xff)
            {
                ir_remote_cmd_t command = { .addr = addr, .cmd = cmd };
                isrpipe_write(&ir->isrpipe, (void*)&command, sizeof(command));
            }
        }
    }
}

int ir_remote_read(ir_remote_t *ir, ir_remote_cmd_t *command)
{
    int to_read = sizeof(ir_remote_cmd_t);
    if(isrpipe_read(&ir->isrpipe, (void*)command, to_read) != to_read)
    {
        return -1;
    }
    return 0;
}

void ir_remote_init(ir_remote_t *ir, gpio_t pin)
{
    ir->pin = pin;
    ir->data = 0;
    ir->last_rising = 0;
    ir->read_bits = 0;
    ir->started = false;

    isrpipe_init(&ir->isrpipe, ir->isrpipe_buf, sizeof(ir->isrpipe_buf));
    gpio_init_int(ir->pin, GPIO_IN, GPIO_BOTH, ir_remote_isr, ir);
}

main.c

#include <stdio.h>
#include <stdlib.h>

#include "clk.h"
#include "board.h"
#include "periph_conf.h"
#include "timex.h"
#include "xtimer.h"
#include "periph/gpio.h"

#include "ir_remote.h"

ir_remote_t remote;

int main(void) {
    ir_remote_init(&remote, GPIO_PIN(PORT_A, 8));

    ir_remote_cmd_t cmd;
    while(1) {
        if(ir_remote_read(&remote, &cmd)) {
            puts("error");
            return -1;
        }
        printf("Packet addr=0x%X, cmd=0x%X\n", cmd.addr, cmd.cmd);
    }
}
@dp1 dp1 changed the title [RFC] Support for IR remote controls [RFC] Support for IR remote control Apr 7, 2022
@chrysn
Copy link
Member

chrysn commented Apr 8, 2022

I think it'd be good to have IR components in -- they are helpful in bridging the gap between non-networked multimedia installations (or, multimedia installations that do have network capabilities but do not interoperate outside their silos) and the web of things.

A few unsorted questions that'd probably be asked in a PR on this -- just as to ensure that we find a common set of expectations on the work required to support this in RIOT:

  • Is this generic over the decoder chip? (Probably yes; if (and only if) decoders with active-high logic are widespread, it may make sense to add inversion as a parameter).
  • Is the interface (ir_remote_read / ir_remote_cmd_t) generic over different infrared modes? (Probably not; then, naming it ir_nec or something like that might help set the users' expectations).
  • The NEC documentation describes a repeat code. Even if it is not implemented, is the interface suitable so that it can later be extended to also express repetition?
  • Do we have any boards that support this in hardware already? If not, how does one assemble a board that can use this? (An external reference might easily do, but the one you linked doesn't even say which frequency the receiver demodulates or which part that is; the 38kHz part you linked could be part of that description).
  • Do you have any plans for the sending side? (This would also make it easier to test this, as a particular remote controls can be hard to come by).
  • Does a 32-command long receive pipe make sense? That's 1.5 seconds worth of continuous data (if buttons are pressed as fast as possible), or 3 seconds (of a pressed button) if repeat codes are expressed as a 16bit entry as well.

I'd be happy to review this as a PR (just can't make any promises about timeliness), I think I can even dig up some hardware to test it eventually

@dp1
Copy link
Contributor Author

dp1 commented Apr 8, 2022

Hey, thanks for the answer, I'll try to answer your questions below

  • Is this generic over the decoder chip? (Probably yes; if (and only if) decoders with active-high logic are widespread, it may make sense to add inversion as a parameter).

This should be generic over decoders, and from what I could tell the inverted signal is the most common. I could be wrong here, but I don't have other receivers to test.

  • Is the interface (ir_remote_read / ir_remote_cmd_t) generic over different infrared modes? (Probably not; then, naming it ir_nec or something like that might help set the users' expectations).

Indeed, this would work better as ir_nec as both the decoding logic and message contents are NEC - specific

  • The NEC documentation describes a repeat code. Even if it is not implemented, is the interface suitable so that it can later be extended to also express repetition?

Not implemented as of now, but it could be added easily as an additional bool/uint8_t repeat field in the command struct either now or at a later time. In this solution, we'd need to keep the last packet in ir_nec_t and send its addr and cmd fields again with repeat=1.

  • Do we have any boards that support this in hardware already? If not, how does one assemble a board that can use this? (An external reference might easily do, but the one you linked doesn't even say which frequency the receiver demodulates or which part that is; the 38kHz part you linked could be part of that description).

I have the receiver I'm using (the sunfounder one), which has a single output pin which outputs demodulated data. As far as I can tell, receivers always seem to demodulate the data so the modulation frequency doesn't need to be handled in the driver, just the demodulated protocol timings. In any case, some more documentatin on this would definitely make sense.

  • Do you have any plans for the sending side? (This would also make it easier to test this, as a particular remote controls can be hard to come by).

Unfortunately, no. I don't have any hardware that can transmit so I would have no way to test it.

  • Does a 32-command long receive pipe make sense? That's 1.5 seconds worth of continuous data (if buttons are pressed as fast as possible), or 3 seconds (of a pressed button) if repeat codes are expressed as a 16bit entry as well.

Probably not, realistically even a 1 or 2 command pipe would be enough.

@dp1
Copy link
Contributor Author

dp1 commented Jun 3, 2022

Merged in #17935

@dp1 dp1 closed this as completed Jun 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants