红外遥控(NEC协议)编程
参考文章:
一、常用的红外协议
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 位,其他不变。
- Philips RC5 协议 、Sony SIRC 协议、 ITT 协议、JVC协议……
二、NEC红外协议编程要点
- 创建结构体存储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);
}
- 创建一个环型缓冲区,保存红外脉冲的极性和宽度时间。
#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;
}
- 单个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);
}
- 根据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");
}
}
}