1、红外遥控电路

空闲状态:红外发射管不亮,接收头输出高电平。
发送高电平:红外发射管以38KHz频率闪烁发光,接收头输出低电平。
发送低电平:红外发射管不亮,接收头输出高电平。
在NEC协议中,信息传输是基于38KHz载波,也就是说红外线是以载波的方式传递。
2、红外遥控协议
1)、逻辑0定义:
"红外发射头"产生一个逻辑0:发送载波560us + 不发送载波560us;
"红外接收头"收到的逻辑0:560us低电平+560us高电平。

2)、逻辑1定义:
"红外发射头"产生一个逻辑1:发送载波560us + 不发送载波1680us;波形图如下:
"红外接收头"收到的逻辑1:560us低电平+1680us高电平

3)、引导码定义:
红外发射头产生一个引导码:发送载波9ms +不发送载波4.5ms。波形图如下:
"红外接收头"收到一个引导码:9ms的低电平+4.5ms高电平。

4)、地址码定义:
"地址码"为8位,表示"被控制的设备地址"。
5)、地址反码定义:
地址反码为8位,是"地址码"按位取反。
6)、命令码定义:
"命令码"为8位,表示"发送的按键值"。
7)、命令反码定义:
命令反码为8位,是"命令码"按位取反。
8)、重复码定义:
当用户长时间按住遥控器的某个按键时,NEC协议会发送重复码,而不是重复发送完整的数据帧。
"红外发射头"产生一个重复码:发送载波9ms +不发送载波2.25ms +发送载波560us +不发送载波97.94ms。波形图如下:

"红外接收头"收到低电平:9ms,高电平:2.25ms,低电平:560μs,高电平:97.94ms。波形图如下:

根据上面的波形图,红外接收使用TIM2_CH3引脚捕获红外接收头的输出引脚的高电平时间,当红外发射头停止发送载波时,红外接收头输出为高电平。
3、红外接收头根据接收到的高电平时间来接收数据的原理:
1)、通过检测引导码中的4.5毫秒的特征电平时间,就可以判断红外接收头开始接收新数据。
2)、通过检测重复码中的2.25毫秒的特征电平时间,就可以判断红外接收头接收完成。
3)、通过检测重复码中的560微秒的特征电平时间,就可以判断红外接收头收到逻辑0。
4)、通过检测重复码中的1680微秒的特征电平时间,就可以判断红外接收头收到逻辑1。
#define Read_TIM2_CH3_PIN() PBin(10) //红外数据输入脚
u16 HighLevelTimeValue; //捕获到的高电平时间值
u8 Remote_RX_CompleteFlag; //1表示接收到数据
u8 Remote_RX_StartFlag; //1表示接收到引导码
u8 Remote_RX_RiseFlag; //1表示接收到上升沿
u8 DuplicationCode_Cnt; //"重复码"计数器
u32 Remote_RX_Data=0; //红外接收到的数据
u8 RepeatCount=0; //按键按下的次数
//函数功能:红外遥控初始化:设置TIM2为输入捕获,并使能TIM2更新中断和输入捕获中断
//APB1时钟为72MHz
//arr:自动重装值。
//psc:时钟预分频数
//TIM_CKD_DIV1:定时器时钟 = 输入频率
//TIM_CKD_DIV2:定时器时钟 = 输入频率/2
//TIM_CKD_DIV4:定时器时钟 = 输入频率/4
//Remote_Receiver_Init(10000,72);//当arr=10000,psc=72时,则为10ms,误差为1us;
//计算公式:arr*psc/72000000/1,当arr=1000,psc=72时,则为1ms,误差为1us;
void Remote_Receiver_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//设置TIM2定时器的APB1外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能PORTB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能重映射功能
//TIM2_CH3位于PB10引脚
GPIO_PinRemapConfig(GPIO_PartialRemap2_TIM2,ENABLE);
//使能TIM2引脚映射
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
//禁止JTAG功能,把PB3,PB4作为普通IO口使用
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //选择PIN10,是TIM2_CH3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//设置引脚的最高输出速率为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_10);//PB10输出高电平
TIM_TimeBaseStructure.TIM_Period = (arr-1);
//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =(psc-1);
//设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//设置时钟分割:TDTS = Tck_tim
//计算公式:arr*psc/72000000/1,当arr=1000,psc=72时,则为1ms,误差为1us;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
//TIM向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
//根据指定的参数初始化TIMx的时间基数单位
TIM_ICInitStructure.TIM_Channel = TIM_Channel_3;
//选择输入端IC3映射到TI3上
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
//TIM输入1、2、3或4被选中,分别连接到IC1、IC2、IC3或IC4,这里是TIM2_CH3
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
// 配置输入分频,不分频
TIM_ICInitStructure.TIM_ICFilter = 0X3;
//IC4F=0011配置输入滤波器,8个定时器时钟周期滤波
TIM_ICInit(TIM2, &TIM_ICInitStructure); //初始化TIM2的输入捕获通道
TIM_Cmd(TIM2,ENABLE ); // 使能定时器2
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择中断源为TIM2_IRQn
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
//设置抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //设置响应优先级为3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //设置中断使能
NVIC_Init(&NVIC_InitStructure);
//根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_ITConfig( TIM2,TIM_IT_Update|TIM_IT_CC3,ENABLE);
//允许更新中断,允许CC3IE捕获中断
TIM_ClearITPendingBit(TIM2,TIM_IT_Update|TIM_IT_CC3);
//清除TIM2更新中断标志位和输入捕获标志位
Remote_RX_CompleteFlag=0;
Remote_RX_StartFlag=0;
Remote_RX_RiseFlag=0;
DuplicationCode_Cnt=0;
}
//函数功能:处理红外键盘
// 返回值:
// 0,没有任何按键按下
// 其他,按下的按键键值.
u8 Remote_Receiver_Scan(void)
{
u8 sta=0;
u8 t1,t2,addressFlag;
if(Remote_RX_CompleteFlag==1)
//Remote_RX_CompleteFlag=1标记已经完成一次按键的键值信息采集
{
printf("Remote_RX_Data=0x%08X ",Remote_RX_Data);
//识别码 + 识别码反码 + 键值 + 键值反码
t1=Remote_RX_Data>>24; //得到地址码
t2=(Remote_RX_Data>>16)&0xff; //得到地址反码
addressFlag=0;
if( (t1==(u8)~t2) && t1 == REMOTE_ID ) addressFlag=1;
//检验遥控识别码(ID)及地址
if(addressFlag==1)
{
t1=Remote_RX_Data>>8;//键值
t2=Remote_RX_Data; //键值反码
if(t1==(u8)~t2)
{
sta = t1;// 键值正确
DuplicationCode_Cnt = 0;
Remote_RX_RiseFlag=0;
Remote_RX_Data = 0;
Remote_RX_StartFlag=0;
Remote_RX_CompleteFlag=0;
}
}
if( (sta==0)||( Remote_RX_StartFlag==0 ) )
//按键数据错误/也有可能是遥控已经没有按下
{
Remote_RX_CompleteFlag=0;//清除接收到有效按键标识
RepeatCount=0; //清除按键次数计数器
}
}
return sta;
}
//函数功能:定时器2中断服务程序,10ms溢出一次
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET)//TIM2产生更新中断
{
if( Remote_RX_StartFlag==1 )//Remote_RX_StartFlag=1表示收到引导码
{
Remote_RX_RiseFlag=0;//取消上升沿已经被捕获标记
if( DuplicationCode_Cnt==0X00 )// "重复帧"计数器为0
Remote_RX_CompleteFlag=1;
//Remote_RX_CompleteFlag=1标记已经完成一次按键的键值信息采集
if( DuplicationCode_Cnt<14 ) //"重复帧"计数器,小于14
DuplicationCode_Cnt++;//遥控器在发送中
else//DuplicationCode_Cnt是"重复帧"计数器,等于15
{
Remote_RX_StartFlag=0;//清空引导标识
DuplicationCode_Cnt=0;//"重复帧"计数器清零
}
}
}
if(TIM_GetITStatus(TIM2,TIM_IT_CC3)!=RESET)//TIM2产生捕获中断
{
if( Read_TIM2_CH3_PIN() )//上升沿捕获
{
TIM_OC3PolarityConfig(TIM2,TIM_ICPolarity_Falling);
//CC3P=1设置为下降沿捕获
TIM_SetCounter(TIM2,0);
//一旦捕获到上升沿,则清空定时器值,为的是在下降沿处读取高电平的时间值
//引导码:9ms高电平 + 4.5ms低电平,合计13.5ms
//0码 :560us高电平 + 560us低电平,合计1.125ms
//1码 :560us高电平 + 1.6875ms低电平,合计2.25ms
//结束码 :560us高电平
Remote_RX_RiseFlag=1;//Remote_RX_RiseFlag=1表示捕获到上升沿
}
else//下降沿捕获
{
HighLevelTimeValue=TIM_GetCapture3(TIM2);
//读取CCR3也可以清CC3IF标志位,这个值是"捕获到的高电平时间"
TIM_OC3PolarityConfig(TIM2,TIM_ICPolarity_Rising);
// CC3P=0 设置为上升沿捕获
if(Remote_RX_RiseFlag==1)
{
if(Remote_RX_StartFlag==1)//接收到了引导码
{
if(HighLevelTimeValue>300&&HighLevelTimeValue<800) {//红外接收头收到的逻辑0:560us低电平+560us高电平。
Remote_RX_Data<<=1; // 左移一位.
Remote_RX_Data|=0; // 接收到0,最低为添加0
}
else if(HighLevelTimeValue>1400&&HighLevelTimeValue<1800)
{//红外接收头收到的逻辑1:560us低电平+1680us高电平
Remote_RX_Data<<=1; // 左移一位.
Remote_RX_Data|=1; // 接收到1,最低为添加1
}
else if(HighLevelTimeValue>2200&&HighLevelTimeValue<2600)
{//红外接收头收到一个重复帧:9ms低电平 + 2.25ms高电平
RepeatCount++; //按键次数增加1次
DuplicationCode_Cnt=0;
//清空计时器,标记已经完成一次按键的键值信息采集
}
}
else if(HighLevelTimeValue>4200&&HighLevelTimeValue<4700)
{//红外接收头收到一个引导码:9ms的低电平+4.5ms高电平。
Remote_RX_StartFlag=1;
//Remote_RX_StartFlag=1表示收到引导码
RepeatCount=0;//清除按键次数计数器
}
}
Remote_RX_RiseFlag=0;//Remote_RX_RiseFlag=0表示捕获到下降沿
}
}
TIM_ClearITPendingBit(TIM2,TIM_IT_Update|TIM_IT_CC3);
//清除TIM2更新中断标志位和输入捕获标志位
}
4、红外发射头工作的原理:
使用TIM4通道2输出PWM波,占空比为50%,重装在频率为38KHz。
PWM输出的周期为ARR*PR*1/72000000/1,单位为秒。
令ARR=1894,PR=1,则周期为:
T=1894*1/72/1=26.30555555555556us
f=(72000000*1)/(1894*1)= 38014.78352692714Hz=38.01478352692714KHz
#define REMOTE_Send_Enable() TIM_Cmd(TIM4, ENABLE) //使能定时器4
#define REMOTE_Send_Disable() TIM_Cmd(TIM4, DISABLE) //使能定时器4
//函数功能:将PB7配置为TIM4_CH2输出
void TIM4_CH2_Pin_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //使能定时器4时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
//使能GPIO外设和AFIO复用功能模块时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //选择PIN7,是TIM4的CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置引脚为复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//设置引脚的最高输出速率为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//PWM输出的周期为ARR*PR*1/72000000/1,单位为秒
//PWM输出比较极性的宽度为(CCR1_Val/2)*720*1/72000000/1,单位为秒
//CCR1_Val表示PWM信号电平跳变值
//Remote_Send_Init(1894,1)//周期为26.30555555555556us,f=38.01478352692714KHz
void Remote_Send_Init(u32 ARR,u32 PR)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM4_CH2_Pin_Config();
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//设置时钟分频系数,TIMx_CR1中的CKD[1:0]=00,Tdts=Tck_int;
TIM_TimeBaseInitStructure.TIM_Period = ARR-1;//设置周期为ARR-1
TIM_TimeBaseInitStructure.TIM_Prescaler = PR-1;//设置TIMx预分频器为PR-1
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
//设置定时器计数模式:向上计数模式,TIM1_CR1中的CMS[1:0]=00,TIM1_CR1中的DIR=0
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
//设置周期次数计数寄存器的值为1,在PWM中,该设置不影响;
//PWM输出的周期为ARR*PR*1/72000000/1,单位为秒
//PWM输出比较极性的宽度为(ARR/2)*720*1/72000000/1,单位为秒
TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);
/////////////////////////配置PWM通道2///////////////////////////////////
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择TIM脉冲宽度调制模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//使能输出比较状态
TIM_OCInitStructure.TIM_Pulse = ARR/2;
//设置跳变值,当计数器计数到这个值时,电平发生跳变,这里设置占空比为50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
//设置PWM输出比较极性为为高电平
TIM_OC2Init(TIM4, &TIM_OCInitStructure);
//根据TIM_OCInitStructure所指定的参数初始化TIM4通道2
TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);
//PWM输出比较2预装载使能
TIM_ARRPreloadConfig(TIM4, ENABLE); //使能TIM4重载寄存器ARR
TIM_CtrlPWMOutputs(TIM4,ENABLE); //TIM4定时器的PWM主输出模式使能
// TIM_SetCompare2(TIM4,ARR/2);//占空比为50%
// TIM_Cmd(TIM4, ENABLE); //使能定时器4
// TIM_Cmd(TIM4, DISABLE); //失能TIM4,防止第一个脉冲异常
REMOTE_Send_Disable();//不使能发送
}
//函数功能:发送引导码
void Remote_Send_L(void)
{
REMOTE_Send_Enable(); //使能发送
delay_us(8890);//9000
REMOTE_Send_Disable();//不使能发送
delay_us(4490);//4500
}
//函数功能:发送重复码
void Remote_Send_DuplicationCode(void)
{
REMOTE_Send_Enable(); //使能发送
delay_us(8890);//9000
REMOTE_Send_Disable();//不使能发送
delay_us(2240);//2250
REMOTE_Send_Enable(); //使能发送
delay_us(550);//560
REMOTE_Send_Disable();//不使能发送
delay_us(97930);//97940
}
//函数功能:发送一个逻辑1
void Remote_Send_High_Level(void)
{
REMOTE_Send_Enable(); //使能发送
delay_us(550);//560
REMOTE_Send_Disable();//不使能发送
delay_us(1670);//1680
}
//函数功能:发送一个逻辑0
void Remote_Send_Low_Level(void)
{
REMOTE_Send_Enable(); //使能发送
delay_us(550);//560
REMOTE_Send_Disable();//不使能发送
delay_us(550);//560
}
//函数功能:发送一个字节
void Remote_Send_a_Byte(uint8_t data)
{
signed char i;
for (i = 7; i >=0; i--)
{
if (data&(1<<i))
{
Remote_Send_High_Level();
}
else
{
Remote_Send_Low_Level();
}
}
}
//函数功能:发送一个按键值
void Remote_Send_Data(uint8_t keyValue)
{
u8 address;
address=REMOTE_Send_ID;
Remote_Send_L();
Remote_Send_a_Byte(address);
Remote_Send_a_Byte(~address);
Remote_Send_a_Byte(keyValue);
Remote_Send_a_Byte(~keyValue);
Remote_Send_DuplicationCode();
Remote_Send_DuplicationCode();
Remote_Send_DuplicationCode();
delay_ms(2000);
}
5、main.c如下:
#include "stm32f10x.h"//使能uint8_t,uint16_t,uint32_t,uint64_t,int8_t,int16_t,int32_t,int64_t
#include "stdio.h" //getchar(),putchar(),scanf(),printf(),puts(),gets(),sprintf()
#include "string.h" //使能strcpy(),strlen(),memset()
#include "delay.h"
#include "USART4.h"
#include "LED.h"
#include "Remote_Receiver.h"
#include "Remote_Send.h"
const char CPU_Reset_REG[]="\r\nCPU reset!\r\n";
int main(void)
{
u8 ret,i;
// uint8_t tmpNumberOf_RemanentLengthBytes;//"解码前剩余长度的字节数"
// SCB->VTOR = 0x8000000;//中断向量表重定义
// SystemInit();
delay_init();//延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
USART4_Serial_Interface_Enable(115200);
printf("%s",CPU_Reset_REG);//调试串口输出"\r\nCPU reset!\r\n"
LED_Init();
LED0_ON();
Remote_Receiver_Init(10000,72);//当arr=10000,psc=72时,则为10ms,误差为1us;
Remote_Send_Init(1894,1);//周期为26.30555555555556us,f=38.01478352692714KHz
delay_ms(3000);
while(1)
{
for(i=0;i<20;i++)
{
Remote_Send_Data(i);
printf("Send %u ",i);
ret=Remote_Receiver_Scan();
printf("ret=0x%02X\r\n",ret);
}
}
}
6、自发自收,测试结果:
