跳转至

IR 使用指南*

第一章 概述*

红外通信协议是一种基于红外线的传输技术。广泛使用的家电遥控器几乎都是采用的红外线传输技术。作为无线局域网的传输方式,红外线方式的最大优点是不受无线电干扰,且它的使用不受国家无线管理委员会的限制。但是,红外线对非透明物体的透过性较差,导致传输距离受限制。

红外协议*

支持 9012/NEC(8bit)/RC5/RC6 四种标准传输协议的接收、发射,其他协议可以使用灵活模式获取协议波形软件进行解析。

9012 协议*

9012 协议采用不同间隔的脉冲信号来对信号进行编码。脉冲信号脉宽为 1Tm(560µs),逻辑‘1’需要 4Tm(2.25ms),逻辑‘0’只需 2Tm。载波频率为 38kHz,建议载波占空比为 ¼ 或 ⅓。

img

Tm = 0.56ms

  • 重发间隔 = 192Tm = 108ms
  • 载波频率 ≈ 38kHz
  • 数据段包含 8 位 customer code 以及 8 位 command code
  • 为提高可靠性,customer code 和 command code 会连发两次

在数据段之前,会先发 8Tm (4.5ms) 的逻辑‘1’,然后跟 8Tm (4.5ms) 的逻辑‘0’(间隔),然后才发 customer codecommand code,customer code 和 command code 均为 LSB 先发。为提高可靠性,customer code 和 command code 会连发两次,第二次的 customer code 跟第一次一样,第二次的 command code 为第一次 command code 的取反,可用于验证接收数据正确性。8 位 customer code 对应程序收发数据的 bit31~bit24 和 bit23~bit16,8 位 command code 对应程序收发数据的 bit8~bit0,command code 取反对应 bit15~bit8。 第二次 command code 之后,跟 1Tm 的逻辑‘1’作为 stop bit,标志传输结束。

img

img

如果按键长按,每隔 192Tm 重发间隔就会发射一个重复命令。重发命令为 8Tm(4.5ms)的逻辑‘1’,跟着 8Tm(4.5ms)的逻辑‘0’再加 1Tm(560µs)的脉冲,最后再跟着 1Tm 的 stop bit。

img

NEC 协议 (8bits)*

NEC 协议采用不同间隔的脉冲信号来对信号进行编码。脉冲信号脉宽为 1Tm(560µs),逻辑‘1’需要 4Tm(2.25ms),逻辑‘0’只需 2Tm。载波频率为 38kHz,建议载波占空比为 ¼ 或 ⅓。

img

Tm = 0.56ms

  • 重发间隔 = 192Tm = 108ms
  • 载波频率 ≈ 38kHz
  • 数据段包含 8 位 customer code 以及 8 位 command code
  • 为提高可靠性,customer code 和 command code 会连发两次

在数据段之前,会先发 16Tm (9ms) 的逻辑‘1’,然后跟 8Tm (4.5ms) 的逻辑‘0’(间隔),然后才发 customer codecommand code,customer code 和 command code 均为 LSB 先发。为提高可靠性,customer code 和 command code 会连发两次,第二次的 customer code 为第一次的取反或者 customer code 的另一半值,第二次的 command code 为第一次 command code 的取反,可用于验证接收数据正确性。8 位 customer code 对应程序收发数据的 bit23~bit16, customer code 取反对应程序收发数据的 bit31~bit24,8 位 command code 对应程序收发数据的 bit8~bit0,command code 取反对应 bit15~bit8。 第二次 command code 之后,跟 1Tm 的逻辑‘1’作为 stop bit,标志传输结束。

img

如果按键长按,每隔 192Tm 重发间隔就会发射一个重复命令。重发命令为 16Tm(9ms)的逻辑‘1’,跟着 4Tm(2.25ms)的逻辑‘0’再加 1Tm(560µs)的脉冲,最后再跟着 1Tm 的 stop bit。

img

img

RC5 协议*

RC5 协议采用曼彻斯特编码,每一 bit 的长度都是 1.68ms(2Tm)。逻辑’0’为 1Tm 的高电平(带载波)在前,跟着 1Tm 的空闲(无载波)。逻辑’1’为 1Tm 的空闲(无载波)在前,跟着 1Tm 的高电平(带载波)。RC5 协议的载波频率为 38kHz,建议载波占空比为 ¼ 或 ⅓。

  • 1 bit-time = 1.688ms
  • Tm = 1 bit-time/2 = 0.844ms
  • 重发间隔 = 4 x 16 x 2Tm = 108ms
  • 载波频率 ≈ 38kHz
  • 数据段包含 5 位 customer code 以及 6 位 command code

img

img

帧结构中,前两 bit 为 start bit,由逻辑’1’组成。第三位是翻转位(Toggle bit),每当按键按下并松开后翻转位将取反,接收机通过判断前后两次接收的翻转位就能知道按键是否一直按下在重复发同样的数值。 之后的 5bit 是 custom code,RC5 中定义位红外设备地址,以 MSB 传输。后 6bit 是 command code,同样 MSB 传输。5 位 customer code 对应程序收发数据的 bit21~bit16,6 位 command code 对应程序收发数据的 bit5~bit0。 一帧数据总共 14bit,总时长 28Tm。个别情况下会先得更短,例如 startbit 的第一 bit 为’1’ ,前半段是无信号的空间隔。如果 Command Code 的最后一 bit 为逻辑’0’ ,则最后一 bit 的后半段为无信号的空间隔。因此实际传输数据的长度会少于 28Tm。

当按键长按,同样的信息将以 128Tm(108ms)为间隔重发。重发的包中翻转位保持不变。

img

img

RC6 协议*

当按键长按,同样的信息将以 128Tm(108ms)为间隔重发。重发的包中翻转位保持不变。

RC6 是 RC5 的继承版本,本模块只支持 RC6 的模式 0。

RC6 采用 36kHz 的红外载波调制,占空比在 25%~50% 之间。传输数据采用曼彻斯特编码,每一 bit 的长度都是 888us(2T)。逻辑’0’为 1T 的空间隔(无载波)在前,跟着 1Tm 的高电平(带载波)。逻辑’1’为 1T 的高电平(带载波)在前,跟着 1T 的空间隔(无载波)。

  • 1 T = 1 x 16 /36K = 444us
  • 1Bit = 2T = 888us
  • 传输总时间 = 22bits = 23.1 ms (message) + 2.7 ms (no signal)
  • 重发间隔 = 240T = 106.7ms

帧结构可以分为 Header 段Control 段Information 段Signal free 段

img

img

Header 段由三部分组成:Leader 位(第一 startbit 和第二 startbit)、模式位翻转位

Leader 位包括 Leader Symbol(LS)和 Start Bit(SB)。Leader Symbol 由 6T(2.666ms)的调制信号以及 2T(0.889ms)的空间隔组成,用于接收机完成增益调整(本模块的应用场景中,接收仅在遥控器与新设备进行学习时,超短距离使用一次,后面不再使用,因此不考虑增益变化)。

img

Start Bit 由逻辑‘1’组成。 模式位 mb2 … mb0 用于配置 RC6 的模式,当前设计仅支持模式 0,因此这三位皆为逻辑‘0’。 翻转位由 2T 调制信号和 2T 空间隔组成。每当按键释放时翻转,用于区别新发射包和重复发射包。

img

普通的数据都包括 1T 的调制信号和 1T 的空间隔。逻辑’0’为 1T 的空间隔(无载波)在前,跟着 1Tm 的高电平(带载波)。逻辑’1’为 1T 的高电平(带载波)在前,跟着 1T 的空间隔(无载波)。

img

Control 段*

Control 段由 8bit 组成,用于设备地址,意味着 RC6 的模式 0 可以支持 256 个设备,Control 段是 MSB 传输。8 位 Control 对应程序收发数据的 bit23~bit16。

Information 段*

Information 段由 8bit 组成,用于命令的传输,意味着可以支持 256 种不同的命令。Information 段是 MSB 传输。8 位 Information 对应程序收发数据的 bit7~bit0。

Signal Free Time*

Signal Free 段指没有任何数据传输的一段时间,用于保证传输之间有足够的间隔,避免接收错误。RC6 中,Signal Free 段的长度为 6T(2.666ms)。

第二章 功能特性*

外设模块特性*

  • 可调发送载波频率及占空比
  • 支持数字和模拟调制/解调
  • 标准模式支持 4 种协议的接收识别:90112/NEC(8bit)/RC5/RC6,接收模块自识别传输协议
  • 自由模式可按照波形时长和电平接收发送,接收发送为独立的 FIFO

载波频率*

调制*

发送时通过寄存器 div_num 和 duty_num 控制计数器产生调制载波,载波频率为 irc_clk/(div_num+1),占空比为(duty_num+1)/(div_num+1)

解调*

接收可解调的最低频率为 irc_clk/16/(16+2*demod_length),当demod_length为0时,最低可解调频率为16.6KHz,模块最高可解调频率为100KHz。

第三章 IR API接口使用*

/**
 * @brief 使用提供的配置初始化红外驱动程序
 *
 * @param ir_cfg 指向红外配置结构的指针
 * @return int 成功返回0,失败返回-1
 */
int gx_ir_init(ir_cfg_t *ir_cfg);

/**
 * @brief 使用给定配置和回调启动标准红外接收
 *
 * @param cfg 指向标准红外接收配置的指针
 * @param cb 标准红外接收的回调函数
 * @return int 成功返回0,失败返回-1
 */
int gx_ir_std_recv_start(ir_std_recv_cfg_t *cfg, ir_std_recv_isr_cb cb);

/**
 * @brief 停止标准红外接收
 *
 * @return int 成功返回0,失败返回-1
 */
int gx_ir_std_recv_stop(void);

/**
 * @brief 使用给定配置和回调启动原始红外接收
 *
 * @param cfg 指向原始红外接收配置的指针
 * @param cb 原始红外接收的回调函数
 * @return int 成功返回0,失败返回-1
 */
int gx_ir_raw_recv_start(ir_raw_recv_cfg_t *cfg, ir_raw_recv_isr_cb cb);

/**
 * @brief 从原始数据缓冲池中获取原始数据
 *
 * @param raw_data 指向原始数据结构的指针
 * @param num 要检索的原始数据数量
 * @return unsigned short 检索到的原始数据数量
 */
unsigned short gx_ir_get_raw_rb_data(ir_raw_data_t *raw_data, unsigned short num);

/**
 * @brief 获取原始数据缓冲池中的原始数据数量
 *
 * @return unsigned short 缓冲池中的原始数据数量
 */
unsigned short gx_ir_get_raw_rb_num(void);

/**
 * @brief 停止原始红外接收
 *
 * @return int 成功返回0,失败返回-1
 */
int gx_ir_raw_recv_stop(void);

/**
 * @brief 重置原始红外接收
 *
 * @return int 成功返回0,失败返回-1
 */
int gx_ir_raw_recv_reset(void);

/**
 * @brief 使用指定的协议、值和重复次数发送标准红外码
 *
 * @param proto 标准红外协议
 * @param value 红外码值
 * @param repeat 1:持续发送重复帧 0:只发送一次重复帧
 * @param cb 标准红外发送的回调函数
 * @return int 成功返回0,失败返回-1
 */
int gx_ir_std_send(ir_std_proto_t proto, unsigned int value, unsigned char repeat, ir_std_send_isr_cb cb);

/**
 * @brief 停止标准红外发送操作
 *
 * @param force 强制停止标志
 * @return int 成功返回0,失败返回-1
 */
int gx_ir_std_send_stop(int force);

/**
 * @brief 使用指定的原始数据、重复数据和回调发送原始红外数据
 *
 * @param data_raw 指向原始数据数组的指针
 * @param data_raw_num 原始数据元素数量
 * @param repeat_raw 指向重复原始数据数组的指针
 * @param repeat_raw_num 重复原始数据元素数量
 * @param cb 原始红外发送的回调函数
 * @return int 成功返回0,失败返回-1
 */
int gx_ir_raw_send(ir_raw_data_t *data_raw, unsigned short data_raw_num, ir_raw_data_t *repeat_raw, unsigned short repeat_raw_num, ir_raw_send_isr_cb cb);

/**
 * @brief 停止原始红外发送操作
 *
 * @param force 强制停止标志
 * @return int 成功返回0,失败返回-1
 */
int gx_ir_raw_send_stop(int force);

/**
 * @brief 设置红外传输的载波配置
 *
 * @param freq 载波频率
 * @param duty_cycle 载波占空比
 * @return int 成功返回0,失败返回-1
 */
int gx_ir_set_send_carrier_cfg(unsigned short freq, unsigned short duty_cycle);

/**
 * @brief 获取红外传输的载波配置
 *
 * @param freq 存储载波频率的指针
 * @param duty_cycle 存储载波占空比的指针
 * @return int 成功返回0,失败返回-1
 */
int gx_ir_get_send_carrier_cfg(unsigned short *freq, unsigned short *duty_cycle);

/**
 * @brief 将原始数据转换为微秒中的周期时间
 *
 * @param raw_data 指向原始数据的指针
 * @return unsigned int 微秒中的周期时间
 */
unsigned int gx_ir_raw_2_cycle_us(ir_raw_data_t *raw_data);

/**
 * @brief 将原始数据转换为微秒中的高电平时间
 *
 * @param raw_data 指向原始数据的指针
 * @return unsigned int 微秒中的高电平时间
 */
unsigned int gx_ir_raw_2_high_us(ir_raw_data_t *raw_data);

/**
 * @brief 将原始数据转换为微秒中的低电平时间
 *
 * @param raw_data 指向原始数据的指针
 * @return unsigned int 微秒中的低电平时间
 */
unsigned int gx_ir_raw_2_low_us(ir_raw_data_t *raw_data);

/**
 * @brief 将载波原始数据转换为微秒中的周期时间
 *
 * @param carrie_raw_data 指向载波原始数据的指针
 * @return unsigned int 微秒中的周期时间
 */
unsigned int gx_ir_carrier_raw_2_cycle_us(ir_raw_data_t *carrie_raw_data);

/**
 * @brief 将载波原始数据转换为微秒中的高电平时间
 *
 * @param carrie_raw_data 指向载波原始数据的指针
 * @return unsigned int 微秒中的高电平时间
 */
unsigned int gx_ir_carrier_raw_2_high_us(ir_raw_data_t *carrie_raw_data);

/**
 * @brief 将载波原始数据转换为微秒中的低电平时间
 *
 * @param carrie_raw_data 指向载波原始数据的指针
 * @return unsigned int 微秒中的低电平时间
 */
unsigned int gx_ir_carrier_raw_2_low_us(ir_raw_data_t *carrie_raw_data);

第四章 IR使用例子参考*

参考 apus\apps\ir_sample

4.1 初始化红外*

#define IR_SEND_DUTY_CYCLE    (30) //30%
#define IR_SEND_CARRIER_FREQ    (38000) //38k

#define IR_RECV_TIMEOUT_US    (100 * 1000) //full frame + repeat frame

#define IR_RAW_RECV_DATA_MAX    (64)
#define IR_RAW_RECV_FRAME_MIN_ITVL_US    (15 * 1000)

#define IR_CARRIER_RAW_RECV_MAX    (16)
#define IR_RAW_RB_POOL_MAX    (128)

static ir_raw_data_t _recv_raw_rb_pool[IR_RAW_RB_POOL_MAX];

ir_cfg_t ir_cfg;

ir_cfg.send_carrier_duty_cycle = IR_SEND_DUTY_CYCLE;
ir_cfg.send_carrier_freq = IR_SEND_CARRIER_FREQ;
ir_cfg.raw_rb_pool = _recv_raw_rb_pool;
ir_cfg.raw_rb_max_num = IR_RAW_RB_POOL_MAX;

gx_ir_init(&ir_cfg);

4.2 发送带协议红外码*

static ir_std_code_t _recv_std_code = {
    .value = 0xbf40f807, /* 8位用户码 + 8位用户反码 + 8位数据码 + 8位数据反码 */
    .proto = 1, /* NEC */
};

static void _ir_send_std_code(int argc, char **argv)
{
    unsigned char repeat = 0;
    char *endp = NULL;

    if(argc > 1) {
        repeat = strtoul(argv[1], &endp, 10);
    }

    printf("[%s]proto: %s, code: 0x%x, repeat: %d\n", __func__, _ir_proto_str[_recv_std_code.proto], _recv_std_code.value, repeat);
    unsigned long long tick = gx_get_time_us();
    if(gx_ir_std_send(_recv_std_code.proto, _recv_std_code.value, repeat, _ir_std_send_isr_cb) < 0) {
        printf("[%s] gx_ir_std_send error!\n", __func__);
    } else {
        printf("send tick: %llu\n", gx_get_time_us() - tick);
    }
}

4.3 接收带红外协议的红外码*

static void _ir_recv_code_proc(app_ir_recv_code_info_t *code_info)
{
    printf("code: 0x%x, proto: %s, repeat: %d, status: 0x%x\n",
            code_info->code.value, _ir_proto_str[code_info->code.proto], code_info->repeat, code_info->status);
    _recv_std_code = code_info->code;
}

static void _ir_std_recv_isr_cb(ir_std_code_t *code, ir_std_recv_status_t status, unsigned char repeat);

static void _ir_std_recv_start(void)
{
    ir_std_recv_cfg_t cfg = {0};

    cfg.command_check_en = 1;
    cfg.custom_check_en = 1;
    cfg.timeout_us = IR_RECV_TIMEOUT_US;

    if(gx_ir_std_recv_start(&cfg, _ir_std_recv_isr_cb) < 0) {
        printf("[%s] gx_ir_std_recv_start error!\n", __func__);
    }
}

4.4 发送未带协议红外波形*

static void _ir_send_raw(int argc, char **argv)
{
    unsigned char repeat = 0;
    char *endp = NULL;
    ir_raw_data_t *repeat_raw = NULL;
    unsigned short repeat_num = 0;

    if(argc > 1) {
        repeat = strtoul(argv[1], &endp, 10);
    }

    if(repeat) {
        repeat_num = _recv_raw.repeat_num;
        repeat_raw = _recv_raw.repeat;
    }
    printf("send data raw num: %d, repeat raw num: %d\n", _recv_raw.data_num, repeat ? repeat_num : 0);

    for(int i = 0; i < _recv_raw.data_num; i++) {
        printf("%3d. data 0x%08x, h: %u us, l: %u us\n", i, _recv_raw.data[i],
                gx_ir_raw_2_high_us(&_recv_raw.data[i]), gx_ir_raw_2_low_us(&_recv_raw.data[i]));
    }

    printf("\n");
    for(int i = 0; i < repeat_num; i++) {
        printf("%3d. repeat 0x%08x, h: %u us, l: %u us\n", i, repeat_raw[i],
                gx_ir_raw_2_high_us(&repeat_raw[i]), gx_ir_raw_2_low_us(&repeat_raw[i]));
    }

    unsigned long long tick = gx_get_time_us();
    if(gx_ir_raw_send(_recv_raw.data, _recv_raw.data_num, repeat_raw, repeat_num, _ir_raw_send_isr_cb) < 0) {
        printf("[%s] gx_ir_raw_send error!\n", __func__);
    } else {
        printf("send tick: %llu\n", gx_get_time_us() - tick);
    }
}

4.5 接收未带协议的红外波形*

  • 超时解析数据帧和重复帧*

以100ms为接收超时,在超时回调中应用代码根据最小帧间隔分出完整帧和重复帧

static void _ir_recv_raw_case_1(unsigned short raw_num)
{
    unsigned short proc_num =  (raw_num <= IR_RAW_RECV_DATA_MAX) ? raw_num : IR_RAW_RECV_DATA_MAX;
    unsigned char frame_num = 0;

    printf("recv raw: %d, proc: %d\n", raw_num, proc_num);

    _recv_raw.data_num = 0;
    _recv_raw.repeat_num = 0;

    for(int i = 0; i < proc_num; i++) {
        ir_raw_data_t data;
        gx_ir_get_raw_rb_data(&data, 1);

        if(frame_num == 0) {
            _recv_raw.data[_recv_raw.data_num++] = data;
            printf("%3d. data 0x%08x, h: %u us, l: %u us\n", i, data,
                    gx_ir_raw_2_high_us(&data), gx_ir_raw_2_low_us(&data));
        } else if(frame_num == 1) {
            _recv_raw.repeat[_recv_raw.repeat_num++] = data;
            printf("%3d. repeat%d  0x%08x, h: %u us, l: %u us\n", i, frame_num, data,
                    gx_ir_raw_2_high_us(&data), gx_ir_raw_2_low_us(&data));
        } else {
            printf("%3d. repeat%d  0x%08x, h: %u us, l: %u us\n", i, frame_num, data,
                    gx_ir_raw_2_high_us(&data), gx_ir_raw_2_low_us(&data));
        }

        if(gx_ir_raw_2_low_us(&data) > IR_RAW_RECV_FRAME_MIN_ITVL_US) {
            frame_num++;
            printf("\n");
        }
    }

    gx_ir_raw_recv_stop();

    printf("recv frame num: %d\n", frame_num);
    printf("data raw len: %d\n", _recv_raw.data_num);
    printf("repeat raw len: %d\n", _recv_raw.repeat_num);
}
  • 以最小帧的间隔接收*

第一次超时回调获取完整帧,后续回调获取重复帧

static void _ir_recv_raw_case_2_4(unsigned short raw_num)
{
    ir_raw_data_t *raw = NULL;
    unsigned short proc_num;

    proc_num = (raw_num <= IR_RAW_RECV_DATA_MAX) ? raw_num : IR_RAW_RECV_DATA_MAX;
    if(_recv_raw_ctx.recv_done_times == 1) {
        _recv_raw.data_num = gx_ir_get_raw_rb_data(_recv_raw.data, proc_num);
        proc_num = _recv_raw.data_num;
        raw = _recv_raw.data;
    } else {
        _recv_raw.repeat_num = gx_ir_get_raw_rb_data(_recv_raw.repeat, proc_num);
        proc_num = _recv_raw.repeat_num;
        raw = _recv_raw.repeat;
    }

    printf("recv raw: %d, proc: %d\n", raw_num, proc_num);
    for(int i = 0; i < proc_num; i++)
    {
        printf("%3d. data 0x%08x, h: %u us, l: %u us\n", i, raw[i],
                gx_ir_raw_2_high_us(&raw[i]), gx_ir_raw_2_low_us(&raw[i]));
    }

    if(_recv_raw_ctx.recv_done_times == 2) {
        gx_ir_raw_recv_stop();

        printf("recv frame num: %d\n", _recv_raw_ctx.recv_done_times);
        printf("data raw len: %d\n", _recv_raw.data_num);
        printf("repeat raw len: %d\n", _recv_raw.repeat_num);

        if(_recv_raw_ctx.recv_raw_case == IR_RECV_RAW_CASE_4) {
            // recv carrier
            ir_raw_recv_cfg_t cfg = {0};

            cfg.rxfifo_threshold = IR_RX_FIFO_THR_HALT;
            cfg.timeout_us = IR_RAW_RECV_FRAME_MIN_ITVL_US;
            cfg.carrier_en = 1;

            if(gx_ir_raw_recv_start(&cfg, _ir_raw_recv_isr_cb) < 0) {
                printf("[%s] gx_ir_raw_recv_start error!\n", __func__);
            }
        }
    }
}
  • 通过载波信号计算载波频率*

static void _ir_recv_carrirer_raw(unsigned short raw_num)
{
    unsigned int freq = 0;
    unsigned int tick_us = 0;

    _recv_raw.data_num = (raw_num <= IR_CARRIER_RAW_RECV_MAX) ? raw_num : IR_CARRIER_RAW_RECV_MAX;
    _recv_raw.data_num = gx_ir_get_raw_rb_data(_recv_raw.data, _recv_raw.data_num);

    gx_ir_raw_recv_stop();

    printf("recv raw: %d, proc: %d\n", raw_num, _recv_raw.data_num);
    for(int i = 0; i < _recv_raw.data_num; i++) {
        printf("%3d. data 0x%08x, h: %u us, l: %u us\n", i, _recv_raw.data[i],
                gx_ir_raw_2_high_us(&_recv_raw.data[i]), gx_ir_raw_2_low_us(&_recv_raw.data[i]));

        tick_us += gx_ir_carrier_raw_2_cycle_us(&_recv_raw.data[i]);
    }

    tick_us /= _recv_raw.data_num; //average tick us
    freq = ((1000000 * 10) / tick_us + 5) / 10;

    printf("carry freq: %u\n", freq);
}