STM32F103c6t6+红外遥控+红外接收模块
- 前言
- 一、什么红外通信协议
- 1.1、NEC 协议
- 1.1-1,NEC 协议 位 的定义
- 1.1-2,红外遥控以NEC 协议数据“单发”的格式
- 1.1-3,红外遥控以NEC 协议“连发”数据格式
- 1.1-4捕获红外信号的思路
- 二、红外接收程序
- 2.1、由外部中断接收红外信号
- 2.1-1、GUA_Infrared_Receiver.c 文件
- 2.1-2、GUA_Infrared_Receiver.h 文件
- 2.1-3、main.c 文件
- 效果演示
- 相关参考资料链接
前言
红外传感只占用了STM32的一个引脚(外部中断引脚)就可以实现无数的数据传输,单片机也就对不同的红外数据进行接收与程序存储的信息进行比较,再在执行主程序·····
下面是本人对红外接收的处理,以及相应的一些简单的主函数执行。

一、什么红外通信协议
1.1、NEC 协议
- 8位地址码和8位用户码;
- 增加8位地址反码和8位用户反码的传输(以确保可靠性);
- PWM脉冲位置调制,以发射红外载波的占空比代表“0”和“1”;
- 载波频率为38Khz;
1.1-1,NEC 协议 位 的定义
如何判断数据的一个位是 “1” 还是 “0” :
- 一个逻辑 “1” 传输需要2.25ms(560us脉冲+1680us低电平);
- 一个逻辑 “0” 的传输需要1.125ms(560us脉冲+560us低电平);
- 所以,每位的周期为 2.25ms 或者 1.12ms ,(分别代表“1”和“0”)

1.1-2,红外遥控以NEC 协议数据“单发”的格式
一个正常的数据包 :
- 开头:发送的是9ms高电平+4.5ms低电平。为引导码。
- 紧接着是:8bit的地址码+8bit地址反码+8bit用户码+8bit用户反码。为32位的数据。

1.1-3,红外遥控以NEC 协议“连发”数据格式
连续发送数据包 :
- 长按红外遥控按键时,每隔 110ms 重复发送一次。但是命令只发送一次,重复发送的是 9ms 高电平+2.25ms 低电平+0.56ms 高电平+低电平

1.1-4捕获红外信号的思路
- 接收程序思路分析: STM32红外接收分析
- 关于理解 NEC 协议+程序设置思路: 红外遥控+定时器计时
- +定时器实现解码红外NEC协议: STM32定时器实现红外接收与解码
二、红外接收程序
2.1、由外部中断接收红外信号
2.1-1、GUA_Infrared_Receiver.c 文件
- 针对大佬的代码进行了自己的理解与修改,增加了解译红外遥控发送的32位数据(地址码+地址反码+用户码+用户反码)的函数。
- 这里最核心的,属红外接收处理时对高低电平时间的判断,经过不断的修正电平维持时间,就可以将红外遥控发送的数据捕获到。
- 针对不同的遥控可能因为单片机频率和红外接收模块各因素,需要修改判断电平维持时间的范围。
处理红外接收的函数
GUA_U8 GUA_Infrared_Receiver_Process(void)
- 在红外接收处理函数处,我增加串口输出的功能,每到一个接收出错退出循环的同时将上次计时的值从串口打印出来,由此知道红外接收输入引脚上电平变化的时间间隔。对不同的遥控按键发送的协议解读有实用价值。
红外接收处理出错时执行
/6 else if(GUA_Infrared_Receiver_Process() == GUA_INFRARED_RECEIVER_ERROR){ //否则、串口显示已经累加的时间(uS)和 解码错误提示Serial_SendNumber(nGUA_Time_Num,4); Serial_SendString(" GUA_INFRARED_RECEIVER_ERROR \r\n");gGUA_InfraredReceiver_Data=0;}
- 还有,红外发送过来的键码解读成功后,将其 地址码+地址反码+用户码+用户反码 都打印在串口上,由此,可以看到用户码(命令键)的十进制,再换算成十六进制后就可以进行比较与传递。
跳转到该子函数执行串口显示
/5 Show_Data(); //显示红外接收到的数据
-
这里对于红外连续发送时的处理比较随便,但是效果还是达到了。不符合该函数判断的红外协议并不会被捕获到。
-
四重对电平维持时间判断关卡决定了数据的保存与否。
#include "GUA_Infrared_Receiver.h"/*********************外部变量************************/
GUA_U32 gGUA_InfraredReceiver_Data = 0; //一个接收红外原始数据的变量(32位)/********************保存红外数据的变量*************/
uint8_t IR_RECEIVE[5]; // 每一项保存一个字节数据
int down16,up16; // 分别保存低十六位和高十六位数据uint8_t IR_State;
uint8_t IR_DataFlag; //单发标志
uint8_t IR_RepeatFlag; //连发标志/*********************内部函数************************/
static void GUA_Infrared_Receiver_IO_Init(void);
static GUA_U16 GUA_Infrared_Receiver_GetHighLevelTime(void);
static GUA_U16 GUA_Infrared_Receiver_GetLowLevelTime(void);//******************************************************************************
//name: GUA_Infrared_Receiver_IO_Init
//introduce: 红外接收的IO初始化
static void GUA_Infrared_Receiver_IO_Init(void)
{ GPIO_InitTypeDef GPIO_InitStructure; //IO结构体
// //失能JTAG和SWD在PB3上的功能使用
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE);
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//时钟使能
RCC_APB2PeriphClockCmd(GUA_INFRARED_RECEIVER_RCC | RCC_APB2Periph_AFIO, ENABLE);//红外接收IO配置GPIO_InitStructure.GPIO_Pin = GUA_INFRARED_RECEIVER_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GUA_INFRARED_RECEIVER_PORT, &GPIO_InitStructure); GPIO_EXTILineConfig(GUA_INFRARED_RECEIVER_PORTSOURCE, GUA_INFRARED_RECEIVER_PINSOURCE);
}//******************************************************************************
//name: GUA_Infrared_Receiver_Exti_Init
//introduce: 红外接收的IO中断初始化
//******************************************************************************
static void GUA_Infrared_Receiver_Exti_Init(void)
{ NVIC_InitTypeDef NVIC_InitStructure;EXTI_InitTypeDef EXTI_InitStructure;//中断配置EXTI_InitStructure.EXTI_Line = GUA_INFRARED_RECEIVER_EXTI_LINE;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling ; EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
}//******************************************************************************
//name: GUA_Infrared_Receiver_Init
//introduce: 红外接收初始化
//******************************************************************************
void GUA_Infrared_Receiver_Init(void)
{GUA_Infrared_Receiver_IO_Init(); //初始化IOGUA_Infrared_Receiver_Exti_Init(); //初始化中断配置
}//******************************************************************************
//name: GUA_Infrared_Receiver_GetHighLevelTime
//introduce: 红外接收获取高电平维持时间
//******************************************************************************
static GUA_U16 GUA_Infrared_Receiver_GetHighLevelTime(void)
{GUA_U16 nGUA_Num = 0;//判断是否一直为高电平
while(GPIO_ReadInputDataBit(GUA_INFRARED_RECEIVER_PORT, GUA_INFRARED_RECEIVER_PIN) == Bit_SET){//超时超时溢出if(nGUA_Num >= 250) {return nGUA_Num;}nGUA_Num++; //计延时20us的次数Delay_us(20); //该延时针对外部晶振8000的STM32C8T6核心板,对于不同类型不同外部晶振的开发板需要根据波形图调整延时,保证20us延时 }return nGUA_Num;
}//******************************************************************************
//name: GUA_Infrared_Receiver_GetLowLevelTime
//introduce: 红外接收获取低电平维持时间
//******************************************************************************
static GUA_U16 GUA_Infrared_Receiver_GetLowLevelTime(void)
{GUA_U16 nGUA_Num = 0;//判断是否一直为低电平while(GPIO_ReadInputDataBit(GUA_INFRARED_RECEIVER_PORT, GUA_INFRARED_RECEIVER_PIN) == Bit_RESET){if(nGUA_Num >= 500) {return nGUA_Num;}nGUA_Num++;delay_us(20);//该延时针对外部晶振8000的STM32C8T6核心板,对于不同类型不同外部晶振的开发板需要根据波形图调整延时,保证低电平延时 }return nGUA_Num;
}//******************************************************************************
//name: GUA_Infrared_Receiver_Process
//introduce: 红外接收的处理函数
// 该函数是关键所在:处理红外信号看接收管OUT引脚电平维持时间,高低电平一定时间后状态变化。
// 依据电平维持时间,判断:引导码、32位数据···
//******************************************************************************
GUA_U16 nGUA_Time_Num;
GUA_U8 nGUA_Data;
GUA_U8 nGUA_Byte_Num;
GUA_U8 nGUA_Bit_Num;
GUA_U32 ContinuousReceiver_Data; //保存上次捕获的32位原始的数据
//
GUA_U8 GUA_Infrared_Receiver_Process(void)
{nGUA_Time_Num = 0;nGUA_Data = 0;nGUA_Byte_Num = 0;nGUA_Bit_Num = 0;//接收引导码9ms的低电平,过滤无用信号>10ms或<8msnGUA_Time_Num = GUA_Infrared_Receiver_GetLowLevelTime();if((nGUA_Time_Num > 500) || (nGUA_Time_Num < 400)){
//1 Serial_SendString("\r\n 1: error occurred \r\n"); //_________return GUA_INFRARED_RECEIVER_ERROR;} //接收引导码4.5ms的高电平,过滤无用信号>5ms或<4ms //250ms时是连续发送信号nGUA_Time_Num = GUA_Infrared_Receiver_GetHighLevelTime();if((nGUA_Time_Num > 250) || (nGUA_Time_Num < 100)) //200<= >=250{
//2 Serial_SendString("\r\n 2: error occurred \r\n"); //_________return GUA_INFRARED_RECEIVER_ERROR;} //接收4字节数据(分别有:用户反码、用户码、地址反码、地址码)for(nGUA_Byte_Num = 0; nGUA_Byte_Num < 4; nGUA_Byte_Num++){//接收位数据(每字节8位)for(nGUA_Bit_Num = 0; nGUA_Bit_Num < 8; nGUA_Bit_Num++){//接收每bit的前0.56ms的低电平,过滤无用信号>1.2ms或<0.40msnGUA_Time_Num = GUA_Infrared_Receiver_GetLowLevelTime();if((nGUA_Time_Num > 60) || (nGUA_Time_Num < 20)){
//3 Serial_SendString("\r\n 3: error occurred \r\n"); //_________return GUA_INFRARED_RECEIVER_ERROR;}//接收每bit的后高电平时长:高电平数据,1.68ms(1.2ms~2.0ms),低电平数据,0.56ms(0.2ms~1ms),过滤其他无用信号nGUA_Time_Num = GUA_Infrared_Receiver_GetHighLevelTime();if((nGUA_Time_Num >=60) && (nGUA_Time_Num < 100)) //25 50{nGUA_Data = 1;}else if((nGUA_Time_Num >=10) && (nGUA_Time_Num < 50)) //50 90{nGUA_Data = 0;}else{if(nGUA_Time_Num == 250) //红外连续发送情况下,退出循环直接赋予上次的数据{IR_RepeatFlag = 1; //连续收发标志置一IR_DataFlag = 0; //单发标志置零gGUA_InfraredReceiver_Data = ContinuousReceiver_Data;return GUA_INFRARED_RECEIVER_OK; }else{
//4 Serial_SendString("\r\n 4: error occurred \r\n"); //_________return GUA_INFRARED_RECEIVER_ERROR;}}//保存数据gGUA_InfraredReceiver_Data <<= 1; gGUA_InfraredReceiver_Data |= nGUA_Data; }}IR_DataFlag = 1; //单次收发标志置一IR_RepeatFlag = 0; //连发标志置零ContinuousReceiver_Data = gGUA_InfraredReceiver_Data;return GUA_INFRARED_RECEIVER_OK;
}//*******************************************************************************
//》》》》》》》》》》》》》》中断处理函数《《《《《《《《《《《《《《《《《《
void EXTI9_5_IRQHandler(void)
{if(EXTI_GetITStatus(GUA_INFRARED_RECEIVER_EXTI_LINE) != RESET){EXTI_ClearITPendingBit(GUA_INFRARED_RECEIVER_EXTI_LINE);EXTI->IMR &= ~(GUA_INFRARED_RECEIVER_EXTI_LINE);if(GUA_Infrared_Receiver_Process() == GUA_INFRARED_RECEIVER_OK) //如果捕获到有效数据{down16=gGUA_InfraredReceiver_Data;up16=gGUA_InfraredReceiver_Data>>16;//-----------从上到下依次为用户反码、用户码、地址反码、地址码IR_RECEIVE[3]=down16;IR_RECEIVE[2]=(down16>>8);IR_RECEIVE[1]=up16;IR_RECEIVE[0]=(up16>>8);//5 Show_Data(); //显示红外接收到的数据}
/*6 else if(GUA_Infrared_Receiver_Process() == GUA_INFRARED_RECEIVER_ERROR){ //否则、串口显示已经累加的时间(uS)和 解码错误提示Serial_SendNumber(nGUA_Time_Num,4); Serial_SendString(" GUA_INFRARED_RECEIVER_ERROR \r\n");gGUA_InfraredReceiver_Data=0;}*/}EXTI->IMR|=(GUA_INFRARED_RECEIVER_EXTI_LINE);
}/***********************调试键码的函数(串口输出)***********************************函数: 显示出已经捕获得到的红外数据,* 如果解码成功,可以获得红外数据(地址码+地址反码+用户码+用户反码)* (通过串口发送相关信息,使用Serial_SendNumber()可以在调试窗口文本类得到十进制格式,* 再将数据十进制换算成十六进制就可以进行比较和传递。)*参数:无
**/
void Show_Data(void)
{
// Serial_SendArray(IR_RECEIVE,32);Serial_SendString("\r\n GUA_INFRARED_RECEIVER_OK ");Serial_SendString("\r\n 用户反码: ");Serial_SendNumber( IR_RECEIVE[3],4);Serial_SendString("\r\n 用户码: ");Serial_SendNumber( IR_RECEIVE[2],4);Serial_SendString("\r\n 地址反码: ");Serial_SendNumber( IR_RECEIVE[1],4);Serial_SendString("\r\n 地址码: ");Serial_SendNumber( IR_RECEIVE[0],4);
}
/************************************五个可获取红外相关数据的函数************************************ @brief 红外遥控获取收到数据帧标志位* @param 无* @retval 是否收到数据帧,1为收到,0为未收到*/
uint8_t IR_GetDataFlag(void)
{if(IR_DataFlag){IR_DataFlag=0;return 1;}return 0;
}
/*** @brief 红外遥控获取收到连发帧标志位* @param 无* @retval 是否收到连发帧,1为收到,0为未收到*/
uint8_t IR_GetRepeatFlag(void)
{if(IR_RepeatFlag){IR_RepeatFlag=0;return 1;}return 0;
}
/*** @brief 红外遥控获取收到的地址数据* @param 无* @retval 收到的地址码数据*/
uint8_t IR_GetAddress(void)
{return IR_RECEIVE[0];
}
/*** @brief 红外遥控获取收到的命令数据* @param 无* @retval 收到的用户码数据*/
uint8_t IR_GetUserCode(void)
{return IR_RECEIVE[2];
}/**** @brief 为获得的红外用户码(命令)数据赋予一个数字身份* @param 无* @retval 收到的用户码的新身份*/
uint8_t IR_CommandSymbol(void)
{unsigned char Num;switch(IR_RECEIVE[2]){case IR_0: Num=0;break; //0 代表 0键:case IR_1: Num= 1;break; //1 代表 1键: case IR_2: Num= 2;break; //2 代表 2键:case IR_3: Num= 3;break; //3 代表 3键: case IR_4: Num= 4;break; //4 代表 4键:case IR_5: Num= 5;break; //5 代表 5键: case IR_6: Num= 6;break; //6 代表 6键:case IR_7: Num= 7;break; //7 代表 7键:case IR_8: Num= 8;break; //8 代表 8键:case IR_9: Num= 9;break; //9 代表 9键:case IR_POWER: Num= 11;break; //11 代表 OnOff键:case IR_MODE: Num= 12;break; //12 代表 Mode键: case IR_MUTE: Num= 13;break; //13 代表 Mute键:case IR_START_STOP: Num= 21;break; //21 代表 Pause键:case IR_PREVIOUS: Num= 22;break; //22 代表 Left键: case IR_NEXT: Num= 23;break; //23 代表 Right键:case IR_EQ: Num= 31;break; //31 代表 EQ键:case IR_VOL_MINUS: Num= 32;break; //32 代表 VolADD键 case IR_VOL_ADD: Num= 33;break; //33 代表 VolDOW键case IR_RPT: Num= 42;break; //42 代表 RPT键: case IR_USD: Num= 43;break; //43 代表 U_SD键:default :break;}return Num;
}
2.1-2、GUA_Infrared_Receiver.h 文件
- 得到的红外遥控键码(用户码)进行了(十六进制的)宏定义。
- 而主要利用的是,我将用户码进行了重新的命名,针对不同的按键__相应有代表数字。其由函数:uint8_t IR_CommandSymbol(void) 来实现。
- 如何利用这些函数,请看主函数。
//******************************************************************************
//name: GUA_Infrared_Receiver.h
//******************************************************************************
#ifndef _GUA_INFRARED_RECEIVER_H_
#define _GUA_INFRARED_RECEIVER_H_#include "main.h"
.............略了一些定义
.............
//红外接收引脚
#define GUA_INFRARED_RECEIVER_PORT GPIOB
#define GUA_INFRARED_RECEIVER_PIN GPIO_Pin_5
#define GUA_INFRARED_RECEIVER_RCC RCC_APB2Periph_GPIOB//中断
#define GUA_INFRARED_RECEIVER_EXTI_LINE EXTI_Line5
#define GUA_INFRARED_RECEIVER_PORTSOURCE GPIO_PortSourceGPIOB
#define GUA_INFRARED_RECEIVER_PINSOURCE GPIO_PinSource5#define TRUE 0
#define FALSE 1#define GUA_INFRARED_RECEIVER_OK 0
#define GUA_INFRARED_RECEIVER_ERROR 1/*********************外部变量************************/
extern GUA_U32 gGUA_InfraredReceiver_Data; //一个接收红外原始数据的变量(32位)/********************* 函数声明 ************************/
extern void GUA_Infrared_Receiver_Init(void);
extern GUA_U8 GUA_Infrared_Receiver_Process(void);
void Show_Data(void);//**************************************************************
//》》》》》》》》》》能在主函数里调用的函数《《《《《《《《《《《
//**************************************************************uint8_t IR_GetDataFlag(void); // 是否按一次 (单发)
uint8_t IR_GetRepeatFlag(void); // 是否为连按 (连发)
uint8_t IR_GetAddress(void); // 地址码
uint8_t IR_GetUserCode(void); // 用户码(按键命令)
uint8_t IR_CommandSymbol(void); // 将用户码转换出一个对应的数字身份//***********************************************************************
//***********************************************************************
//从“调试键码的函数”获得相应用户码,并转换位16进制后,对遥控发送的键码(用户码)进行宏定义
#define IR_POWER 0xA2
#define IR_MODE 0x62
#define IR_MUTE 0xE2
#define IR_START_STOP 0x22
#define IR_PREVIOUS 0x02
#define IR_NEXT 0xC2
#define IR_EQ 0xE0
#define IR_VOL_MINUS 0xA8
#define IR_VOL_ADD 0x90
#define IR_0 0x68
#define IR_RPT 0x98
#define IR_USD 0xB0
#define IR_1 0x30
#define IR_2 0x18
#define IR_3 0x7A
#define IR_4 0x10
#define IR_5 0x38
#define IR_6 0x5A
#define IR_7 0x42
#define IR_8 0x4A
#define IR_9 0x52#endif
2.1-3、main.c 文件
- 很显然,IR_GetDataFlag()和 IR_GetRepeatFlag()两个函数是判断是否有接收到红外信号。
- IR_GetAddress()和IR_GetUserCode()两个函数就是分别获得红外数据中“地址码”和“用户码”。
- IR_CommandSymbol()函数是根据遥控器上按键对“用户码”进行了重新命名定义,就方便于使用与记忆。
#include "main.h"uint8_t Num;
uint8_t Address;
uint8_t Command;
uint8_t One_More =1,Command_early;
uint8_t Information;void Click_Button_(void);
void Continuous_Button_(void);int main(void)
{OLED_Init();LED_Init();Serial_Init();GUA_Infrared_Receiver_Init(); //红外接收初始化Serial_SendString("\r\n Is OK \r\n");OLED_ShowString(1, 1,"User Add CMD ");OLED_ShowString(2, 1,"0000 0000 00 ");LED2_OFF();LED2_OFF();while (1){if(IR_GetDataFlag()) //若红外单发{LED2_Turn();OLED_ShowNum(2,1,IR_GetUserCode(),4);OLED_ShowNum(2,6,IR_GetAddress(),4);OLED_ShowNum(2,12,IR_CommandSymbol(),2);if(((int )IR_CommandSymbol() >= 0) && ((int )IR_CommandSymbol() <= 9) ){Serial_SendString("\r\n 你按下了——键 "); // 打印按键0到9Serial_SendNumber(IR_CommandSymbol(),1);}else if(IR_CommandSymbol() == 6) //当然该判断是不会执行了,因为已经满足了前面一个的判断条件{Serial_SendString("\r\n 你按下的是键“6”");}else if(IR_CommandSymbol() == 11){Serial_SendString("\r\n 你按了开关键");}else if(IR_CommandSymbol() == 12){OLED_ShowString(3,1," Happy Happy");OLED_ShowString(4,2,"Happy every day");}else if(IR_CommandSymbol() == 22){Serial_SendString("\r\n 上一曲");}else if(IR_CommandSymbol() == 23){Serial_SendString("\r\n 下一曲");}}if(IR_GetDataFlag() || IR_GetRepeatFlag()) //若红外单发或连发{if(IR_CommandSymbol() == 32){Serial_SendString("\r\n 音量- -");}else if(IR_CommandSymbol() == 33){Serial_SendString("\r\n 音量+ +");}}}
}

效果演示

红外接收视频演示
相关参考资料链接
1、典型的 NEC 协议传输格式:红外遥控原理
3、该参考程序的原版来源: STM32之红外接收