单片机外部中断+定时器实现红外遥控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;
			}
		}
	}
}
相关推荐
最后一个bug39 分钟前
STM32MP1linux根文件系统目录作用
linux·c语言·arm开发·单片机·嵌入式硬件
wenchm1 小时前
细说STM32F407单片机IIC总线基础知识
stm32·单片机·嵌入式硬件
嵌入式lover2 小时前
STM32项目之环境空气质量检测系统软件设计
stm32·单片机·嵌入式硬件
kenwblack2 小时前
STM32 SPI读取SD卡
stm32·单片机
兰_博3 小时前
51单片机驱动1602液晶显示
单片机·嵌入式硬件·51单片机
深圳市青牛科技实业有限公司 小芋圆3 小时前
开关电源特点、分类、工作方式
前端·科技·单片机·物联网·分类·数据挖掘·新能源
我qq不是451516523 小时前
单片机优先级
单片机·嵌入式硬件
相醉为友5 小时前
在开发嵌入式系统时,尤其是处理大数时,会遇到取值范围的问题。51单片机通常没有内建大整数支持,因此我们需要采用不同的方法来解决这一问题
单片机·嵌入式硬件·51单片机
1101 11016 小时前
STM32-笔记10-手写延时函数(SysTick)
笔记·stm32·单片机
极客小张6 小时前
基于STM32的智慧农业控制系统设计:python可视化、UART、I2C、TCP/HTTP技术
python·stm32·单片机·物联网·tcp/ip·毕业设计·课程设计