红外遥控(NEC协议)编程

参考文章:

  1. 红外遥控协议-NEC协议
  2. NEC Infrared Transmission Protocol
  3. 红外遥控器-IR-linux驱动(基于imx6ull平台)

一、常用的红外协议

1. NEC 协议

          在这里插入图片描述

  上图为典型的 NEC 协议传输格式,起始位(引导码)为 9ms 低+4.5ms 高组成(与上图时序相反),有效数据为地址 + 地址反码 + 命令 + 命令反码(还有一个562.5µs的结束脉冲)。反码的作用是用来校准前面 的地址和命令,如果对可靠性不感兴趣 ,也可以去掉取反的数据,或者将地址和命令扩展到16 位。 如传输的地址数据为 10011010 ,需要注意的是先发低位地址再发高位地址,因此该波形的地址为 01011001=0X59 ,同理,命令为 00010110=0X16。
          在这里插入图片描述

  长按键时,如上图所示,每隔110ms 重复发送一次,但是命令只发送一次, 重复发送的是 9ms 低电平 +2.25ms 高电平 +0.56ms 低电平 + 0.56ms/1.68ms的高电平。扩展协议如下图所示:扩展协议只是将地址改为16 位,其他不变。
在这里插入图片描述


  1. Philips RC5 协议 、Sony SIRC 协议、 ITT 协议、JVC协议……



二、NEC红外协议编程要点

  1. 创建结构体存储I/O口的电平和红外脉冲宽度时间,并用定时器记录当前系统时间。
//创建结构体存储I/O口的电平和红外脉冲宽度时间
typedef struct irda_raw_event {
	int pol; /* 极性 */
	int duration;  /* 脉冲宽度, us */
}irda_raw_event, *p_irda_raw_event;

//初始化定时器,将
void timer_init(void)
{
	/* 设置TIMER0的时钟 */
	/* Timer clk = PCLK / {prescaler value+1} / {divider value} 
	             = 50000000/(4+1)/2
	             = 5000000
	   每减1, 对应0.2us
	   每减5, 对应1us
	   50000-->0 : 对应 10ms
	 */
	TCFG0 = 4;  /* Prescaler 0 = 4, 用于timer0,1 */
	TCFG1 &= ~0xf; /* MUX0 : 1/2 */

	/* 设置TIMER0的初值 */
	TCNTB0 = 50000;  /* 10Ms中断一次 */

	/* 加载初值, 启动timer0 */
	TCON |= (1<<1);   /* Update from TCNTB0 & TCMPB0 */

	/* 设置为自动加载并启动 */
	TCON &= ~(1<<1);
	TCON |= (1<<0) | (1<<3);  /* bit0: start, bit3: auto reload */

	/* 设置中断 */
	register_irq(10, timer_irq);
}

//定时器中断,10ms进入一次定时器中断
//g_system_time_10ms_cnt 全局变量,记录系统时间
void timer_irq(void)
{
	g_system_time_10ms_cnt++;
}

//防止us的系统时间溢出,返回值定义为unsigned long long 
unsigned long long get_system_time_us(void)
{
	unsigned long long us = (50000 - TCNTO0)/5;
	return g_system_time_10ms_cnt * 10 * 1000 + us;
}

unsigned int delta_time_us(unsigned long long pre, unsigned long long now)
{
	return (now - pre);
}

  1. 创建一个环型缓冲区,保存红外脉冲的极性和宽度时间。
#define NEXT_PLACE(i) ((i+1)&0x3FF)  // 等价于取%

static irda_raw_event g_events[1024];
static int g_r = 0;
static int g_w = 0;

static int is_ir_event_buf_empty(void)
{
	return g_r == g_w;
}

static int is_ir_event_buf_full(void)
{
	return NEXT_PLACE(g_w) == g_r;
}

int ir_event_put(p_irda_raw_event pd)
{
	if (is_ir_event_buf_full())
		return -1;
	g_events[g_w] = *pd;
	g_w = NEXT_PLACE(g_w);
	return 0;
}

int ir_event_get(p_irda_raw_event pd)
{
	if (is_ir_event_buf_empty())
		return -1;
	*pd = g_events[g_r];
	g_r = NEXT_PLACE(g_r);
	return 0;
}


int ir_event_get_timeout(p_irda_raw_event pd, int timeout_us)
{
	while (timeout_us--)
	{
		if (!is_ir_event_buf_empty())
		{
			*pd = g_events[g_r];
			g_r = NEXT_PLACE(g_r);
			
			return 0;
		}
		udelay(1);
	}
	return -1;
}

  1. 单个I/O口使能中断(双边沿触发中断),根据两个连续中断边沿的系统时间差来计算红外脉冲宽度时间,并根据电平判断为pulse/space。
/* 
 * 配置GPIO, 注册中断
 * 在中断处理函数里:
      记录中断发生的时间,
      跟上次中断的时间比较, 计算出脉冲宽度
      读取引脚极性
      把数据放入环型缓冲区
 */
 /* 先实现GPIO的基本操作 */
static void irda_data_cfg_as_eint(void)
{
	/* 配置为中断引脚 */
	GPFCON &= ~(3<<2);
	GPFCON |= (2<<2);

	/* 设置中断触发方式: 双边沿触发 */
	EXTINT0 |= (7<<4);  /* eint1 */

}

static int irda_data_get(void)
{
	if (GPFDAT & (1<<1))
		return 1;
	else
		return 0;
}

static unsigned long long g_last_time = 0;
void irda_irq(int irq)
{
	/* 在中断处理函数里:
		 记录中断发生的时间,
		 跟上次中断的时间比较, 计算出脉冲宽度
		 读取引脚极性
		 把数据放入环型缓冲区
	*/
	irda_raw_event event; 
	unsigned long long cur = get_system_time_us();
	
	event.duration = delta_time_us(g_last_time, cur);
	event.pol      = !irda_data_get(); //脉冲中断的前一个电平状态
	ir_event_put(&event); //存储到环型缓冲区,地址传递
	g_last_time = cur;
}


/* 注册中断 */
void irda_init(void)
{
	irda_data_cfg_as_eint();
	register_irq(1, irda_irq);
}

  1. 根据NEC协议,解析双向队列中的红外数据。
*
 * 从环型缓冲区中获得脉冲数据,
 * 解析得出address, data
 */

#define DURATION_BASE  563
#define DURATION_DELTA (DURATION_BASE/2) //如引导头9ms前后

#define DURATION_HEAD_LOW    (16*DURATION_BASE)
#define DURATION_HEAD_HIGH   (8*DURATION_BASE)
#define DURATION_REPEAT_HIGH (4*DURATION_BASE)
#define DURATION_DATA_LOW    (1*DURATION_BASE)
#define DURATION_DATA1_HIGH  (3*DURATION_BASE)
#define DURATION_DATA0_HIGH  (1*DURATION_BASE)
#define DURATION_END_LOW     (1*DURATION_BASE)

static int duration_in_margin(int duration, int us)
{
	if ((duration > (us - DURATION_DELTA)) && (duration < us + DURATION_DELTA))
		return 1;
	else
		return 0;
}

/*
 * 返回值: 0-得到数据, 1-得到重复码, -1 : 失败
 */
int irda_nec_read(int *address, int *data)
{
	irda_raw_event event;	
	int i;
	unsigned int val = 0;
	unsigned char byte[4];

	while (1)
	{
		if (0 == ir_event_get(&event))  // 常量放在前,便于当出现0 = ir_event_get(&event)时,编译器报错
		{
			/* 解析数据 */
			/* 1. 判断是否为9MS的低脉冲 */
			if (event.pol == 0 && \
				duration_in_margin(event.duration, DURATION_HEAD_LOW))
			{
				/* 进行后续判断 */
				/* 2. 读下一个高脉冲数据 */
				if (0 == ir_event_get_timeout(&event, 10000))
				{
					/* 3. 判断它是否4.5ms的高脉冲
					 *    或者 2.25ms的高脉冲
					 */
					if (event.pol == 1 && \
						duration_in_margin(event.duration, DURATION_HEAD_HIGH))
					{
						/* 4.5ms的高脉冲 */
						/* 4. 重复解析32位数据 */
						for (i = 0; i < 32; i++)
						{
							/* 5. 读0.56ms的低脉冲 */
							if (0 == ir_event_get_timeout(&event, 10000))
							{
								if (event.pol == 0 && \
									duration_in_margin(event.duration, DURATION_DATA_LOW))
								{
									/* 6. 读下一个数据, 判断它是 0.56ms/1.68ms的高脉冲 */
									if (0 == ir_event_get_timeout(&event, 10000))
									{
										if (event.pol == 1 && \
											duration_in_margin(event.duration, DURATION_DATA1_HIGH))
										{
											/* 得到了bit 1 */
											val |= (1<<i);
										}
										else if (event.pol == 1 && \
												duration_in_margin(event.duration, DURATION_DATA0_HIGH))
										{
											/* 得到了bit 0 */
										}
										else
										{
											printf("%s %d\n\r", __FUNCTION__, __LINE__);
											return -1;
										}
									}
									else
									{
										printf("%s %d\n\r", __FUNCTION__, __LINE__);
										return -1;
									}
								}
								else
								{
									printf("%s %d\n\r", __FUNCTION__, __LINE__);
									return -1;
								}
							}
							else
							{
								printf("%s %d\n\r", __FUNCTION__, __LINE__);
								return -1;
							}
						}

						/* 7. 得到了32位数据, 判断数据是否正确 */
						byte[0] = val & 0xff;
						byte[1] = (val>>8) & 0xff;
						byte[2] = (val>>16) & 0xff;
						byte[3] = (val>>24) & 0xff;

						//printf("get data: %x %x %x %x\n\r", byte[0], byte[1], byte[2], byte[3]);
						byte[1] = ~byte[1];
						byte[3] = ~byte[3];
						
						if (byte[0] != byte[1])
						{
							/* 有些遥控器不完全遵守NEC规范 */
							//printf("%s %d\n\r", __FUNCTION__, __LINE__);
							//return -1;
						}
						if (byte[2] != byte[3])
						{
							printf("%s %d\n\r", __FUNCTION__, __LINE__);
							return -1;
						}
						*address = byte[0];
						*data    = byte[2];
						return 0;
						
					}
					else if (event.pol == 1 && \
						duration_in_margin(event.duration, DURATION_REPEAT_HIGH))
					{
						/* 2.25ms的高脉冲 */
						return 1;  /* 重复码 */
					}
					else
					{
						printf("%s %d\n\r", __FUNCTION__, __LINE__);
						return -1;  /* 错误 */
					}
				}
			}
			else
			{
				//printf("%s %d\n\r", __FUNCTION__, __LINE__);
				return -1; /* 有效数据未开始 */
			}
		}
	}
}

void irda_nec_test(void)
{
	int address;
	int data;
	int ret;
	
	irda_init();

	while (1)
	{
		ret = irda_nec_read(&address, &data);
		if (ret == 0)
		{
			printf("irda_nec_read: address = 0x%x, data = 0x%x\n\r", address, data);
		}
		else if (ret == 1)
		{
			printf("irda_nec_read: repeat code\n\r");
		}
	}
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值