espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.05k stars 7.13k forks source link

How can I make modifications to the rmt/ir_nec_transceiver example to work with EliteScreens (IDFGH-12946) #13902

Open pavel808 opened 1 month ago

pavel808 commented 1 month ago

Answers checklist.

General issue report

I am struggling to adapt the the rmt/ir_nec_transceiver example code to be able to send IR commands to my EliteScreens wall controller.

There is support for this wall controller in the Arduino IRremoteESP8266 library which works well:

Having reverse-engineered the code as best I can. The carrier frequency appears the same. However, we can see that the timings are different : onemark is 470us, onespace is 1214us and vice-versa for zeromark and zerospace. The duty cycle is 50% and not 33% like in NEC. :

// Constants
const uint16_t kEliteScreensOne = 470;
const uint16_t kEliteScreensZero = 1214;
const uint16_t kEliteScreensGap = 29200;

#if SEND_ELITESCREENS
/// Send an Elite Screens formatted message.
/// Status: BETA / Probably Working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendElitescreens(uint64_t data, uint16_t nbits, uint16_t repeat) {
  // Protocol uses a constant bit time encoding.
  sendGeneric(0, 0,  // No header.
              kEliteScreensOne, kEliteScreensZero,
              kEliteScreensZero, kEliteScreensOne,
              0, kEliteScreensGap, data, nbits, 38000, true, repeat, 50);
}

I created a new function for this in ir_nec_encoder.c to try and create the correct encoder for the wall controller as follows:

esp_err_t rmt_new_ir_elitescreen_encoder(const ir_nec_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
    esp_err_t ret = ESP_OK;
    rmt_ir_nec_encoder_t *nec_encoder = NULL;
    ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
    nec_encoder = rmt_alloc_encoder_mem(sizeof(rmt_ir_nec_encoder_t));
    ESP_GOTO_ON_FALSE(nec_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for ir nec encoder");
    nec_encoder->base.encode = rmt_encode_ir_nec;
    nec_encoder->base.del = rmt_del_ir_nec_encoder;
    nec_encoder->base.reset = rmt_ir_nec_encoder_reset;

    rmt_copy_encoder_config_t copy_encoder_config = {};
    ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &nec_encoder->copy_encoder), err, TAG, "create copy encoder failed");

    // construct the leading code and ending code with RMT symbol format
    nec_encoder->nec_leading_symbol = (rmt_symbol_word_t) {
        .level0 = 1,
        .duration0 = 9000ULL * config->resolution / 1000000,
        .level1 = 0,
        .duration1 = 4500ULL * config->resolution / 1000000,
    };
    nec_encoder->nec_ending_symbol = (rmt_symbol_word_t) {
        .level0 = 1,
        .duration0 = 560 * config->resolution / 1000000,
        .level1 = 0,
        .duration1 = 0x7FFF,
    };

    rmt_bytes_encoder_config_t bytes_encoder_config = {
        .bit0 = {
            .level0 = 1,
            .duration0 = 470 * config->resolution / 1000000, // T0H=470us
            .level1 = 0,
            .duration1 = 1214 * config->resolution / 1000000, // T0L=1214us
        },
        .bit1 = {
            .level0 = 1,
            .duration0 = 1214 * config->resolution / 1000000,  // T1H=1214us
            .level1 = 0,
            .duration1 = 470 * config->resolution / 1000000, // T1L=470us
        },
    };
    ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &nec_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");

    *ret_encoder = &nec_encoder->base;
    return ESP_OK;
err:
    if (nec_encoder) {
        if (nec_encoder->bytes_encoder) {
            rmt_del_encoder(nec_encoder->bytes_encoder);
        }
        if (nec_encoder->copy_encoder) {
            rmt_del_encoder(nec_encoder->copy_encoder);
        }
        free(nec_encoder);
    }
    return ret;
}

Then insideir_nec_transceiver_main.c, I try to send the data as follows :

 ir_nec_encoder_config_t elitescreen_encoder_cfg = {
      .resolution = 1000000,  // 1MHz resolution
 };

 rmt_encoder_handle_t elitescreen_encoder = NULL;
 ESP_ERROR_CHECK(rmt_new_ir_elitescreen_encoder(&elitescreen_encoder_cfg, &elitescreen_encoder));

 ESP_ERROR_CHECK(rmt_enable(tx_channel));

// The up command
 uint64_t payload = 0xFDA2256;

ESP_ERROR_CHECK(rmt_transmit(tx_channel, elitescreen_encoder, &payload, sizeof(payload), &transmit_config));

Unfortunately I am receiving inconsistent garbage on my IR receiver.

I'd appreciate any help in trying to solve this. Thanks.

suda-morris commented 1 month ago

I don't know the protocol used by the EliteScreens, but in your code, I see

  sendGeneric(0, 0,  // No header.
              kEliteScreensOne, kEliteScreensZero,
              kEliteScreensZero, kEliteScreensOne,
              0, kEliteScreensGap, data, nbits, 38000, true, repeat, 50);

The comment "No header" seems like that protocol doesn't need a leading symbol as used in the NEC protocol. So I guess this nec_encoder->nec_leading_symbol is not needed at all.

If you can find a datasheet about the EliteScreen protocol, it can be helpful.

pavel808 commented 1 month ago

I don't know the protocol used by the EliteScreens, but in your code, I see

  sendGeneric(0, 0,  // No header.
              kEliteScreensOne, kEliteScreensZero,
              kEliteScreensZero, kEliteScreensOne,
              0, kEliteScreensGap, data, nbits, 38000, true, repeat, 50);

The comment "No header" seems like that protocol doesn't need a leading symbol as used in the NEC protocol. So I guess this nec_encoder->nec_leading_symbol is not needed at all.

If you can find a datasheet about the EliteScreen protocol, it can be helpful.

Hello @suda-morris . Thanks for your reply. I tried removing the nec_leading_symbol. I also tried removing both nec_leading_symbol and nec_ending_symbolbut in either case nothing gets transmitted then.

Also, where would kEliteScreensGap come into this?

I can't find a datasheet anywhere about the EliteScreen protocol :-/

suda-morris commented 1 month ago

I think you should also rewrite the rmt_encode_ir_nec function, that's all the magic happens for controlling what and how to transmit the RMT symbols.

BYTW, A "gap" can be made up by using a "copy encoder". We also use this approach in the led_strip protocol: https://github.com/espressif/esp-idf/blob/master/examples/peripherals/rmt/led_strip/main/led_strip_encoder.c#L102

pavel808 commented 1 month ago

@suda-morris Thanks for the replies. I have implemented separate functions now to try and encode for the EliteScreens IR protocol.

Using as a reference the Arduino application which sends EliteScreens IR commands as follows:

// values in us
uint16_t kEliteScreensOne = 470;
uint16_t kEliteScreensZero = 1214;
uint16_t kEliteScreensGap = 29200;

sendGeneric(0, 0,  // No header.
              kEliteScreensOne, kEliteScreensZero,
              kEliteScreensZero, kEliteScreensOne,
              0, kEliteScreensGap, data, nbits, 38000, true, repeat, 50);

// Parameters description : 

void IRsend::sendGeneric(const uint16_t headermark, const uint32_t headerspace,
                         const uint16_t onemark, const uint32_t onespace,
                         const uint16_t zeromark, const uint32_t zerospace,
                         const uint16_t footermark, const uint32_t gap,
                         const uint64_t data, const uint16_t nbits,
                         const uint16_t frequency, const bool MSBfirst,
                         const uint16_t repeat, const uint8_t dutycycle) {
  sendGeneric(headermark, headerspace, onemark, onespace, zeromark, zerospace,
              footermark, gap, 0U, data, nbits, frequency, MSBfirst, repeat,
              dutycycle);
}

Here is what I have implemented in my IR transceiver, but still getting garbage on the receiver. Do you see anything wrong with this? Thanks in advance.

static size_t rmt_encode_ir_elitescreens(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
    rmt_ir_encoder_t *elite_encoder = __containerof(encoder, rmt_ir_encoder_t, base);
    rmt_encode_state_t session_state = RMT_ENCODING_RESET;
    rmt_encode_state_t state = RMT_ENCODING_RESET;
    size_t encoded_symbols = 0;
    ir_nec_scan_code_t *scan_code = (ir_nec_scan_code_t *)primary_data;
    rmt_encoder_handle_t copy_encoder = elite_encoder->copy_encoder;
    rmt_encoder_handle_t bytes_encoder = elite_encoder->bytes_encoder;
    switch (elite_encoder->state) {
    case 0: // send leading code
        // No leading code in the case for EliteScreens
         elite_encoder->state = 1;
    // fall-through
    case 1: // send address
        encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &scan_code->address, sizeof(uint16_t), &session_state);
        if (session_state & RMT_ENCODING_COMPLETE) {
            elite_encoder->state = 2; // we can only switch to next state when current encoder finished
        }
        if (session_state & RMT_ENCODING_MEM_FULL) {
            state |= RMT_ENCODING_MEM_FULL;
            goto out; // yield if there's no free space to put other encoding artifacts
        }
    // fall-through
    case 2: // send command
        encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &scan_code->command, sizeof(uint16_t), &session_state);
        if (session_state & RMT_ENCODING_COMPLETE) {
            elite_encoder->state = 3; // we can only switch to next state when current encoder finished
        }
        if (session_state & RMT_ENCODING_MEM_FULL) {
            state |= RMT_ENCODING_MEM_FULL;
            goto out; // yield if there's no free space to put other encoding artifacts-
        }
    // fall-through
    case 3: // No ending code, but send GAP of 29200 us
        encoded_symbols += copy_encoder->encode(copy_encoder, channel, &elite_encoder->reset_code,
                                                sizeof(elite_encoder->reset_code), &session_state);
        if (session_state & RMT_ENCODING_COMPLETE) {
            elite_encoder->state = RMT_ENCODING_RESET; // back to the initial encoding session
            state |= RMT_ENCODING_COMPLETE;
        }
        if (session_state & RMT_ENCODING_MEM_FULL) {
            state |= RMT_ENCODING_MEM_FULL;
            goto out; // yield if there's no free space to put other encoding artifacts
        }
    }
out:
    *ret_state = state;
    return encoded_symbols;
}

static esp_err_t rmt_del_ir_elitescreens_encoder(rmt_encoder_t *encoder)
{
    rmt_ir_encoder_t *elite_encoder = __containerof(encoder, rmt_ir_encoder_t, base);
    rmt_del_encoder(elite_encoder->copy_encoder);
    rmt_del_encoder(elite_encoder->bytes_encoder);
    free(elite_encoder);
    return ESP_OK;
}

static esp_err_t rmt_ir_nec_elitescreens_reset(rmt_encoder_t *encoder)
{
    rmt_ir_encoder_t *elite_encoder = __containerof(encoder, rmt_ir_encoder_t, base);
    rmt_encoder_reset(elite_encoder->copy_encoder);
    rmt_encoder_reset(elite_encoder->bytes_encoder);
    elite_encoder->state = RMT_ENCODING_RESET;
    return ESP_OK;
}

esp_err_t rmt_new_ir_elitescreen_encoder(const ir_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
    esp_err_t ret = ESP_OK;
    rmt_ir_encoder_t *elite_encoder = NULL;
    ESP_GOTO_ON_FALSE(config && elite_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
    elite_encoder = rmt_alloc_encoder_mem(sizeof(rmt_ir_encoder_t));
    ESP_GOTO_ON_FALSE(elite_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for ir nec encoder");
    elite_encoder->base.encode = rmt_encode_ir_elitescreens;
    elite_encoder->base.del = rmt_del_ir_elitescreens_encoder;
    elite_encoder->base.reset = rmt_ir_nec_elitescreens_reset;

    rmt_copy_encoder_config_t copy_encoder_config = {};
    ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &elite_encoder->copy_encoder), err, TAG, "create copy encoder failed");

    rmt_bytes_encoder_config_t bytes_encoder_config = {
        .bit0 = {
            .level0 = 1,
            .duration0 = 470 * config->resolution / 1000000, // T0H=470us
            .level1 = 0,
            .duration1 = 1214 * config->resolution / 1000000, // T0L=1214us
        },
        .bit1 = {
            .level0 = 1,
            .duration0 = 1214 * config->resolution / 1000000,  // T1H=1214us
            .level1 = 0,
            .duration1 = 470 * config->resolution / 1000000, // T1L=470us
        },
    };
    ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &elite_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");

    // GAP
    uint32_t reset_ticks = config->resolution / 1000000 * 29200 / 2; // reset code duration defaults to 29200 us
    elite_encoder->reset_code = (rmt_symbol_word_t) {
           .level0 = 0,
           .duration0 = reset_ticks,
           .level1 = 0,
           .duration1 = reset_ticks,
   };

    *ret_encoder = &elite_encoder->base;
    return ESP_OK;
err:
    if (elite_encoder) {
        if (elite_encoder->bytes_encoder) {
            rmt_del_encoder(elite_encoder->bytes_encoder);
        }
        if (elite_encoder->copy_encoder) {
            rmt_del_encoder(elite_encoder->copy_encoder);
        }
        free(elite_encoder);
    }
    return ret;
}