【stm32单片机基础】红外NEC协议解码

【stm32单片机基础】红外NEC协议解码


前言

红外通信协议是一种基于红外线的传输技术,广泛使用的家电遥控器几乎都是采用的红外线传输技术,由于红外线为不可见光,对环境影响很小,红外线遥控不会影响其他家用电器,也不会影响临近的无线电设备。红外遥控的编码方式目前广泛使用的是: PWM(脉冲宽度调制)的 NEC 协议和 PhilipsPPM(脉冲位置调制) 的 RC-5 协议的。本文分享NEC协议接收端的解码程序。

红外NEC协议

通信协议分为发送端和接收端,接收端的波形与发射端刚好相反。
NEC IR 协议使用 32 位帧格式对密钥进行编码,如下所示

1、NEC 帧格式

地址码0地址码1命令码命令码反码
LSB-MSB(0-7)LSB-MSB(8-15)LSB-MSB(16-23)LSB-MSB(24-31)

在标准的NEC协议中,地址码1为地址码0的反码,而在许多遥控器中,地址码0和地址码1共同作为红外遥控器的编码值。

2、发射端的波形


每个位都使用如图所示的脉冲距离进行传输。
逻辑“0”:562.5μs高电平,562.5μs低电平,总时长为1.125ms
逻辑“1”:562.5μs高电平,1.6875ms低电平,总时长为2.25ms

在遥控器上按某个键时,传输的消息将按顺序包含以下内容:

引导码:持续9ms 高电平,4.5ms低电平,作为启动信号;
紧接着是32bit的数据,按照上述的NEC帧格式的顺序;最后以562.5μs脉冲高电平结尾,表示一帧消息传输结束。
数据位的四个字节首先发送最低有效位
下图示例展示了NEC 红外传输帧的格式,以地址为 00h (00000000b) 和 ADh (10101101b) 的命令码为例。
传输一个消息帧总共需要 67.5ms。它需要27ms来传输16位地址(地址+地址反码)和16位命令(命令+命令反码)。
在这里插入图片描述
重复码
如果遥控器上的键保持按下状态,则会发射重复码,通常在完整的一帧消息结束后约40ms后发送;重复码将继续以 108 ms的间隔发出,直到红外遥控按键被释放。
重复码按顺序包含以下内容:

  • 9ms 前导高电平
  • 2.25ms的低电平
  • 562.5μs的高电平来标记一帧重复码的结束。

波形如下图所示:

发送初始消息帧后两个重复码的传输

3、接收端程序解码

由于接收端的波形与发射端的波形刚好相反,在下述程序中,只需要一个定时器即可解码红外NEC协议,包含了红外重复码的检测,可区别红外遥控长按和短按事件。


#define IR_IN  GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1)   // PA1 红外接收DQ引脚
unsigned char ir_code[4];      // 解码值保存变量
unsigned char ir_decode_ok_flag = RESET;    // 解码成功标志位

/*
    NEC红外编码: 引导码 + 地址码 + 地址码(取反) + 数据 + 数据(取反)
      引导吗:0.56ms(低电平) + 2.25ms(高电平)
       数据1: 0.56ms(低电平) + 1.12ms(高电平)
*/
// 红外解码程序,100us定期执行就可以,将该函数放在100us的定时器中即可。
void Ir_Decode(void)
{
    static unsigned int l_cnt = 0;                            // 低电平时间计数
    static unsigned int h_cnt = 0;                            // 高电平时间计数
    static unsigned int l_cnt_save = 0;                       // 保存低电平时长
    static unsigned int h_cnt_save = 0;                       // 保存高电平时长
    static unsigned char falling_edge_valid_flag = RESET;      // IR电平由高变低标志位
    static unsigned char rcv_sync_ok_flag = RESET;             // 同步码接收成功标志位
    static unsigned char bit_value = 0;                        // 位解码值
    static unsigned char bit_rcv_cnt = 0;                      // 位接收个数变量
    if( RESET == IR_IN )
    {
        if( 0 == l_cnt )    // IR由高变低后立马记录上次测得的高电平时长
        {
            h_cnt_save = h_cnt;
            falling_edge_valid_flag = SET;
        }
        
        
        l_cnt ++;
        if( l_cnt > 1600 )    // 防止计数溢出
        {
            l_cnt = 1600;
        }
        h_cnt = 0;  // 计数清零
				
    }
    else
    {
        if( 0 == h_cnt )   // IR由低变高后立马记录上次测得的低电平时长
        {
            l_cnt_save = l_cnt;
        }
        
        h_cnt ++;
        if( h_cnt > 1600 )   // 防止计数溢出
        {
            h_cnt = 1600;
        }
				
        l_cnt = 0;  // 计数清零
				
				
		if(ir_decode_ok_flag == 1)
		{
				if(h_cnt > 1200)
				ir_decode_ok_flag = 2;		//  短按
		}
       
    }
    
    if( SET == falling_edge_valid_flag )
    {
        falling_edge_valid_flag = RESET;
        
        /* 位解码 */
        if( ((l_cnt_save >= 3)&&(l_cnt_save <= 9)) &&          // 560us低电平, 560us高电平
            ((h_cnt_save >= 3)&&(h_cnt_save <= 9)) )
        {
            bit_value = 0;
        }
        else if( ((l_cnt_save >= 3)&&(l_cnt_save <= 9)) &&    // 560us低电平,1680us高电平
                  ((h_cnt_save >= 14)&&(h_cnt_save <= 20)) )
        {
            bit_value = 1;
        }
        else
        {                    
            bit_value = 2;
        }
        
        if( SET == rcv_sync_ok_flag )
        {
            if((1 == bit_value) || (0 == bit_value) )
            {
                if( bit_rcv_cnt < 8 )
                {
                    ir_code[0] |= (bit_value<< (bit_rcv_cnt%8));
                }
                else if( bit_rcv_cnt < 16 )
                {
                    ir_code[1] |= (bit_value<< (bit_rcv_cnt%8));
                }
                else if( bit_rcv_cnt < 24 )
                {
                    ir_code[2] |= (bit_value<< (bit_rcv_cnt%8));
                }
                else if( bit_rcv_cnt < 32 )
                {
                    ir_code[3] |= (bit_value<< (bit_rcv_cnt%8));
                }
                
                if( bit_rcv_cnt >= 31 )
                {
                    ir_decode_ok_flag = SET;
                    rcv_sync_ok_flag = RESET; 
                }
                
                bit_rcv_cnt ++;
            }
            else
            {
                rcv_sync_ok_flag = RESET;                 // 位接收错误,重新解码
            }
        } 
				
        if( ((l_cnt_save >= 87)&&(l_cnt_save <= 93))  &&
            ((h_cnt_save >= 42)&&(h_cnt_save <= 48)) )            // 同步码,9ms低电平,4.5ms高电平
        { 
            rcv_sync_ok_flag = SET;
            bit_rcv_cnt = 0;
            ir_code[0] = 0;
            ir_code[1] = 0;
            ir_code[2] = 0;
            ir_code[3] = 0;
        } 
		else if(((l_cnt_save >= 87)&&(l_cnt_save <= 93))  &&
			((h_cnt_save >= 20)&&(h_cnt_save <= 25)) )
		{
				printf("repeate code\r\n");
				ir_decode_ok_flag = 3;		//长按
		}
		
    }     
}
以下是一个基于STM32的C语言程序,用于解码NEC32协议的38KHz红外信号,不处理重复码: ```c #include "stm32f10x.h" #define IR_PIN GPIO_Pin_0 //红外接收器连接的引脚 #define IR_PORT GPIOA //红外接收器连接的端口 #define IR_THRESHOLD 200 //红外信号的阈值 #define NEC_REPEAT_CODE 0xFFFFFFFF //重复码的值 uint32_t irCode = 0; //存储接收到的红外码 uint32_t lastIrCode = 0; //存储上一次接收到的红外码 void delay_us(uint32_t us) { uint32_t startTick = SysTick->VAL; uint32_t ticks = us * (SystemCoreClock / 1000000); while (ticks > 0) { uint32_t currentTick = SysTick->VAL; if (currentTick < startTick) { ticks -= (startTick - currentTick); } else { ticks -= (startTick + SysTick->LOAD - currentTick); } startTick = currentTick; } } void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) != RESET) { //检测到红外信号的上升沿 EXTI_ClearITPendingBit(EXTI_Line0); irCode = 0; for (int i = 0; i < 32; i++) { //接收32位红外码 while (GPIO_ReadInputDataBit(IR_PORT, IR_PIN) == RESET); //等待信号变成高电平 uint32_t highTime = SysTick->VAL; while (GPIO_ReadInputDataBit(IR_PORT, IR_PIN) != RESET); //等待信号变成低电平 uint32_t lowTime = SysTick->VAL; if (highTime - lowTime > IR_THRESHOLD) { //检测到信号的高电平 irCode |= (1UL << i); //将第i位设置为1 } } if (irCode != NEC_REPEAT_CODE && irCode == ~lastIrCode) { //检查红外码是否有效 lastIrCode = irCode; //保存上一次接收到的红外码 uint8_t address = (irCode >> 24) & 0xFF; //解析出地址码 uint8_t command = (irCode >> 16) & 0xFF; //解析出命令码 printf("Address: %02X, Command: %02X\n", address, command); //将解析结果输出到串口 } } } int main(void) { GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //配置红外接收器的引脚为输入模式,上拉输入 GPIO_InitStructure.GPIO_Pin = IR_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(IR_PORT, &GPIO_InitStructure); //配置外部中断线0,用于检测红外信号的上升沿 EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); //配置外部中断线0的中断 NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); //配置系统滴答定时器,用于微秒级延时 SysTick_Config(SystemCoreClock / 1000000); while (1) { //程序主循环 } } ``` 该程序使用STM32F10x系列的微控制器,连接一个红外接收器到PA0上,通过读取红外接收器的电平值来接收红外信号。当检测到红外信号的上升沿时,程序会接收32位红外码,并解析出地址码和命令码。程序使用printf函数将解析结果输出到串口。需要注意的是,该程序不处理重复码,因此在接收到重复码时不会进行解码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值