单片机外部中断+定时器实现红外遥控NEC协议解码

单片机外部中断+定时器实现红外遥控NEC协议解码

概述

  • 红外(Infrared,IR)遥控,是一种通过调制红外光实现的无线遥控器,常用于家电设备:电视机、机顶盒等等。
  • NEC协议采用PPM(Pulse Position Modulation,脉冲位置调制)的形式进行编码,数据的每一位(Bit)脉冲长度为560us,由38KHz的载波脉冲 (carrier burst) 进行调制。

解码过程

  • 单片机定时器100us定时
  • 单片机外部中断设为下降沿触发
  • IR接收头输出脚作为单片机外部中断信号输入
  • 每操作一次遥控器按键会收到33个中断信号,通过判断定时器计数值范围解析遥控码

参考代码

ir_nec.h

c/c++ 复制代码
#ifndef __IR_NEC_H__
#define __IR_NEC_H__

#include "app.h"

#define IR_USER_CODE_A       (0x00)    // 遥控用户码
#define IR_USER_CODE_B       (0xFF)    // 遥控用户码反码

#define IR_KEYCODE_PWR          (0x45)
#define IR_KEYCODE_MENU         (0x47)
#define IR_KEYCODE_TEST         (0x44)
#define IR_KEYCODE_BACK         (0x43)
#define IR_KEYCODE_UP           (0x40)
#define IR_KEYCODE_DOWN         (0x19)
#define IR_KEYCODE_LEFT         (0x07)
#define IR_KEYCODE_RIGHT        (0x09)
#define IR_KEYCODE_ENTER        (0x15)
#define IR_KEYCODE_CANCEL       (0x0D)
#define IR_KEYCODE_0            (0x16)
#define IR_KEYCODE_1            (0x0C)
#define IR_KEYCODE_2            (0x18)
#define IR_KEYCODE_3            (0x5E)
#define IR_KEYCODE_4            (0x08)
#define IR_KEYCODE_5            (0x1C)
#define IR_KEYCODE_6            (0x5A)
#define IR_KEYCODE_7            (0x42)
#define IR_KEYCODE_8            (0x52)
#define IR_KEYCODE_9            (0x4A)

enum {
    E_IR_KEYCODE_NONE=0,    // 无效遥控码值
    E_IR_KEYCODE_VALUE,     // 有效遥控码值
    E_IR_KEYCODE_REPEAT,    // 遥控重复码值
    E_IR_KEYCODE_MAX
};

struct ir_keycode_t {
    uint8_t isvalid:4;  // 是否有效    
    uint8_t repeat:4;   // 重复码
    uint8_t keycode;    // 按键值
};

void ir_nec_driver_init(void);

struct ir_keycode_t ir_nec_driver_keycode(void);

#endif

ir_nec.c

c/c++ 复制代码
#include "rjm8l151s_crg.h"
#include "rjm8l151s_gpio.h"
#include "rjm8l151s_vic.h"
#include "rjm8l151s_delay.h"
#include "rjm8l151s_timer01.h"

#include "app_config.h"
#include "ir_nec.h"
#include "debug.h"
#include "gpio.h"
#include "app.h"

/*
T=0.0000015     # 定时器计数周期  16M晶振时间 12/(16000000/2分频)=0.0000015
_t = 0.00001    # 目标定时周期
X=_t/T          # TICK值
print("T:", T)
print("X:", X)
*/


#define TIMER1_TICK         133  //100us
#define TIMERMODE_VALUE     TIMERMODE2  //8位重载模式,最大计数256

// #define TIMER1_TICK 1333  //1ms
// #define TIMERMODE_VALUE     TIMERMODE0  //13位定时模式,最大计数8192

// #define TIMER1_TICK 13330  //10ms
// #define TIMERMODE_VALUE     TIMERMODE1  //16位定时模式,最大计数65536

enum {
    E_IR_STATUS_IDLE=0,     // 等待解码
    E_IR_STATUS_START,      // 开始解码 
    E_IR_STATUS_DONE,       // 解码完成
    E_IR_STATUS_UNKNOW
};

struct ir_nec_t {
    uint8_t status;         // 开始接收
    uint8_t bit_index;      // 位索引
    uint8_t flg_repeat;     // 重复码标记
    uint8_t keycode[4];     // 4字节遥控码:用户码+用户码反码+键值码+键值反码
    uint16_t timer_cnt;     // 定时器记数值
    uint16_t tmrcnt[33];    // 接收到的时间数据
};

static struct ir_nec_t ir_nec = {0};

void timer1_init(void)
{
	TIMER01_InitTypeDef  TIMER01_InitStructure;

	RCC_Sccm1_ClockCmd(RCC_SCCM1_TIMER1,ENABLE); //定时器1 时钟使能

	TIMER01_InitStructure.Timer01Mode   =TIMERMODE_VALUE; //16位定时模式,最大计数65536
	TIMER01_InitStructure.Timer01Period =TIMER1_TICK; 
	TIMER01_InitConfig(TIMER1,&TIMER01_InitStructure);

	TIMER01_Cmd(TIMER1,ENABLE);

	/*中断配置*/
	TIMER01_IntCmd(TIMER1,ENABLE);
}

void ir_nec_driver_init(void)
{
    timer1_init();

    RCC_Sccm1_ClockCmd(RCC_SCCM1_GPIO,ENABLE);

    // TOODO: IR红外接收接口
	/*只有P0和P1口可以配置为电平触发,其他端口只能配置为沿触发*/
	IO_FUN_Config( 	GPIO_P4,GPIO_Pin_1,GPIO_FUNCTION_DF0); //配置引脚为GPIO功能
	IO_INPUT_Enable(GPIO_P4,GPIO_Pin_1);            //配置引脚为GPIO输入模式
	IO_INT_Config(  GPIO_P4,GPIO_Pin_1, falling);	//需要外接接下拉电阻	
	IO_INT_Enable(  GPIO_P4,GPIO_Pin_1);
	IRQ_Enable(IT_GPIO4);

    ir_nec.status = E_IR_STATUS_IDLE;
    ir_nec.bit_index = 0;
    ir_nec.flg_repeat = 0;
    ir_nec.timer_cnt = 0;
}


uint8_t ir_nec_driver_decoder(void)
{
    uint8_t i,j=0;
    uint8_t id=0;
    uint8_t _bit=0;
    if (E_IR_STATUS_DONE == ir_nec.status) {
        // log_d("IR bit_index:%d\n", (int)ir_nec.bit_index);
        ir_nec.keycode[0]=0x00;
        ir_nec.keycode[1]=0x00;
        ir_nec.keycode[2]=0x00;
        ir_nec.keycode[3]=0x00;

        #if 0 // for debug
        for (i=0; i<33; i++) {
            log_d("IR [%d]:%d\n", (int)i, (int)ir_nec.tmrcnt[i]);
        }
        #endif

        id=1;   // 第一个索引值是引导码时间,之后是遥控码的脉冲时间
        for (i=0; i<4; i++) {
            // 循环4次解码4字节数据
            for (j=0; j<8; j++) {
                // 解码1个字节数据
                if ((ir_nec.tmrcnt[id]>=8) && (ir_nec.tmrcnt[id]<=15)) { // bit 0
                    _bit = 0;
                } else if ((ir_nec.tmrcnt[id]>=18) && (ir_nec.tmrcnt[id]<=25)) { // bit 1
                    _bit = 1;
                }
                ir_nec.keycode[i] |= (_bit<<j);
                id++;
            }
        }
        // log_d("0x%02X,0x%02X,0x%02X,0x%02X\n", (int)ir_nec.keycode[0], (int)ir_nec.keycode[1], (int)ir_nec.keycode[2], (int)ir_nec.keycode[3]);
        ir_nec.status = E_IR_STATUS_IDLE;
        return E_IR_KEYCODE_VALUE;
    } else if (ir_nec.flg_repeat == 1) {
        // log_d("Repeat 0x%02X,0x%02X,0x%02X,0x%02X\n", (int)ir_nec.keycode[0], (int)ir_nec.keycode[1], (int)ir_nec.keycode[2], (int)ir_nec.keycode[3]);
        ir_nec.flg_repeat = 0;
        return E_IR_KEYCODE_REPEAT;        
    }

    return E_IR_KEYCODE_NONE;
}


struct ir_keycode_t ir_nec_driver_keycode(void)
{
    uint8_t ret=0;
    struct ir_keycode_t keycode={0};
    ret = ir_nec_driver_decoder();

    // TODO: 过滤指定用户码
    if ((ir_nec.keycode[0] != IR_USER_CODE_A)&&(ir_nec.keycode[0] != IR_USER_CODE_B)) {
        ret = E_IR_KEYCODE_NONE;
    }

    if (E_IR_KEYCODE_NONE == ret) {
        keycode.isvalid = 0;
        keycode.keycode = 0x00;
        keycode.repeat = 0;
    } else if (E_IR_KEYCODE_VALUE == ret) {
        keycode.isvalid = 1;
        keycode.keycode = ir_nec.keycode[2];
        keycode.repeat = 0;
    } else if (E_IR_KEYCODE_REPEAT == ret) {
        keycode.isvalid = 1;
        keycode.keycode = ir_nec.keycode[2];
        keycode.repeat = 1;
    }
	return keycode;
}


void Interrupt_GPIO4 (void) interrupt 10     //GPIO4中断服务程序
{
	P4_INT_REG = 0xff;

    if (E_IR_STATUS_START == ir_nec.status) {
        if ((ir_nec.timer_cnt<120)&&(ir_nec.timer_cnt>100)) {
            // TODO: 接收到重复码
            ir_nec.bit_index=0;
            ir_nec.flg_repeat = 1;
        } else if ((ir_nec.timer_cnt<150)&&(ir_nec.timer_cnt>110)) {
            // TODO: 接收到引导码
            ir_nec.bit_index=0;
        }
        ir_nec.tmrcnt[ir_nec.bit_index] = ir_nec.timer_cnt;
        ir_nec.timer_cnt = 0;
        ir_nec.bit_index++;
        if (33 == ir_nec.bit_index) {
            ir_nec.bit_index = 0;
            ir_nec.timer_cnt = 0;
            ir_nec.status = E_IR_STATUS_DONE;
        }
    } else if (E_IR_STATUS_IDLE == ir_nec.status) {
        // TODO: 第一个下降沿
        ir_nec.status = E_IR_STATUS_START;
        ir_nec.bit_index = 0;
        ir_nec.timer_cnt = 0;
    }

    // IO_TEST_Toggle();

#if 0
    if (P4&Bit1_En) {
        // TODO: 下降沿
        P35=1;

    } else {
        // TODO: 上升沿
        P35=0;
    }
#endif
    
}


/*中断方式*/
void Interrupt_TIMRT1 (void) interrupt 3    //TIMRT1中断服务程序
{	
	TF1 = 1; //清标志
#if TIMERMODE_VALUE != TIMERMODE2
	TIMER01_SetPeriod(TIMER1,TIMERMODE_VALUE,TIMER1_TICK);
#endif
    // IO_TEST_Toggle();
    ir_nec.timer_cnt++;
}

main.c

c/c++ 复制代码
main()
{
	struct ir_keycode_t ir_keycode;
	
	// TODO: 单片机系统输出,此处省略。。。


	// TODO: NEC红外解码初始化	
	ir_nec_driver_init()
	
	while(1) {
		ir_keycode = ir_nec_driver_keycode();
		if (ir_keycode.isvalid) {
			if (ir_keycode.repeat) {
				log_d("Repeat ");
			}
			switch (ir_keycode.keycode) {
			case IR_KEYCODE_PWR:
				log_d("IR_KEYCODE_PWR\n");
				break;
			case IR_KEYCODE_MENU:
				log_d("IR_KEYCODE_MENU\n");
				break;
			case IR_KEYCODE_TEST:
				log_d("IR_KEYCODE_TEST\n");
				break;
			case IR_KEYCODE_BACK:
				log_d("IR_KEYCODE_BACK\n");
				break;
			case IR_KEYCODE_UP:
				log_d("IR_KEYCODE_UP\n");
				break;
			case IR_KEYCODE_DOWN:
				log_d("IR_KEYCODE_DOWN\n");
				break;
			case IR_KEYCODE_LEFT:
				log_d("IR_KEYCODE_LEFT\n");
				break;
			case IR_KEYCODE_RIGHT:
				log_d("IR_KEYCODE_RIGHT\n");
				break;
			case IR_KEYCODE_ENTER:
				log_d("IR_KEYCODE_ENTER\n");
				break;
			case IR_KEYCODE_CANCEL:
				log_d("IR_KEYCODE_CANCEL\n");
				break;
			case IR_KEYCODE_0:
				log_d("IR_KEYCODE_0\n");
				break;
			case IR_KEYCODE_1:
				log_d("IR_KEYCODE_1\n");
				break;
			case IR_KEYCODE_2:
				log_d("IR_KEYCODE_2\n");
				break;
			case IR_KEYCODE_3:
				log_d("IR_KEYCODE_3\n");
				break;
			case IR_KEYCODE_4:
				log_d("IR_KEYCODE_4\n");
				break;
			case IR_KEYCODE_5:
				log_d("IR_KEYCODE_5\n");
				break;
			case IR_KEYCODE_6:
				log_d("IR_KEYCODE_6\n");
				break;
			case IR_KEYCODE_7:
				log_d("IR_KEYCODE_7\n");
				break;
			case IR_KEYCODE_8:
				log_d("IR_KEYCODE_8\n");
				break;
			case IR_KEYCODE_9:
				log_d("IR_KEYCODE_9\n");
				break;
			default:
				break;
			}
		}
	}
}
相关推荐
飞凌嵌入式3 小时前
飞凌嵌入式旗下教育品牌ElfBoard与西安科技大学共建「科教融合基地」
嵌入式硬件·学习·嵌入式·飞凌嵌入式
weixin_452600694 小时前
【青牛科技】电流模式PWM控制器系列--D4870
科技·单片机·嵌入式硬件·音视频·智能电表·白色家电电源·机顶盒电源
嵌新程7 小时前
day06(单片机高级)PCB设计
单片机·嵌入式硬件·pcb
stm 学习ing7 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
Jacky(易小天)10 小时前
MongoDB比较查询操作符中英对照表及实例详解
数据库·mongodb·typescript·比较操作符
wenchm11 小时前
细说STM32单片机DMA中断收发RTC实时时间并改善其鲁棒性的另一种方法
stm32·单片机·嵌入式硬件
Loganer12 小时前
MongoDB分片集群搭建
数据库·mongodb
编码追梦人12 小时前
如何实现单片机的安全启动和安全固件更新
单片机
电子工程师UP学堂12 小时前
电子应用设计方案-16:智能闹钟系统方案设计
单片机·嵌入式硬件
飞凌嵌入式13 小时前
飞凌嵌入式T113-i开发板RISC-V核的实时应用方案
人工智能·嵌入式硬件·嵌入式·risc-v·飞凌嵌入式