单片机外部中断+定时器实现红外遥控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;
}
}
}
}