传感器是单片机外围电路中最常见的模块,在搭配了各种形式的传感器电路后,就可以采集到的更多的环境信息。在本章节中,主要介绍呼吸灯、温度传感器、RTC实时时钟以及红外遥控模块的控制使用。

1、PWM实现呼吸灯的效果
1.1 PWM脉冲宽度调制
PWM是利用单片机的数字输出来对模拟电路进行控制的技术,其应用包含电机控制、通信、开关电源等等。PWM是一种对模拟信号进行数字编码的方法,其本质上还是数字信号,也就是在任意时刻端口输出要么是高电平要么就是低电平,电压或者电流是以一种通或端的重复脉冲序列被加到模拟负载上。因此只要带宽足够,理论上任何的模拟信号都可以使用PWM技术进行数字编码,如下图中所示。
在这里插入图片描述

在STM32F1中除了基本定时器TIM6与TIM7外,其它定时器都具备PWM输出的功能,输出PWM就是对外输出脉宽可调的方波信号,该方波信号的频率由自动重装寄存器ARR决定,占空比由比较寄存器CCR决定,因此改变CCR的值就会使PWM输出信号占空比发生改变(占空比=周期内高电平时间/周期总时间)。PWM最常用的输出模式是PWM1和PWM2,其区别如下表中所示。按照PWM计数器CNT的计数方式,可分为边沿对齐模式;中心对齐模式。

1.2 PWM输出配置步骤
使能定时器及端口时钟,并设置引脚复用映射;
调用函数:RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//使能 TIM3 时钟;
TIM3的CH1通道为引脚PA6,在这里需要将这个通道映射到其它IO口上(视硬件电路而定),使用到了GPIO的复用功能,对于TIM3定时器的复用功能如下表中所示。
开启重映射功能需要启动AFIO时钟:RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
调用复用映射功能函数:GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);
配置所映射的管脚模式为复用推挽输出:GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

初始化定时器参数;
调用函数:void TIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
配置细节参见前一章节。
初始化PWM输出参数;
调用函数:oid TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
其中TIMx表示的是PWM输出通道可取值为1/2/3/4;结构体TIM_OCInitTypeDef的成员变量为:

typedef struct
{
	uint16_t TIM_OCMode;			//比较输出模式
	uint16_t TIM_OutputState;	//比较输出使能
	uint16_t TIM_OutputNState;	//比较互补输出使能,高级定时器
	uint16_t TIM_Pluse;			//脉冲宽度
	uint16_t TIM_OCPolarity;		//输出极性
	uint16_t TIM_OCNPolarity;	//互补比较输出极性,高级定时器
	uint16_t TIM_OCIdleState;	//空闲状态下输出状态,高级定时器
	uint16_t TIM_OCNIdleState;	//空闲状态下比较输出状态,高级定时器
}

TIM_OCMode:比较输出模式选择,8种模式,常用PWM1、PWM2;
TIM_OutputState:比较输出使能,使能PWM输出到IO口;
TIM_OCPolarity:输出极性,设定输出通道电平的极性,(高/低);
将TIM3的通道CH2配置为PWM2模式,输出极性高低电平,并使能PWM输出的过程可配置为:

TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enablel;
TIM_OCInit(TIM3, &TIM_OCInitStructure);

开启定时器;
调用函数:void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
修改TIMx_CCRx的值控制占空比;
控制占空比需要通过修改TIMx_CCR1的值实现:void TIM_SetComparel(TIM_TypeDef* TIMx, uint32_t Comparel);
使能TIMx在CCRx上的预装载寄存器;
调用函数:void TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload)
TIMx为选定的定时器;TIM_OCPreload取值为TIM_OCPreloda_Enable或TIM_OCPreload_Disable。
使能TIMx在ARR上的预装载寄存器允许位。
调用函数:void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
1.3 应用示例-呼吸灯
通过TIM3定时器的CH1通道输出一个PWM信号,控制一颗LED由“灭->暗->亮->暗->灭”,不断重复该过程。在项目中需要添加定时器库文件stm32f10x_tim.c以及stm32f10x_tim.h。

pwm.h

#ifndef _pwm_H
#define _pwm_H

#include "system.h" 

void TIM3_CH1_PWM_Init(u16 pre,u16 psc);

#endif

pwm.c

#include "pwm.h"

void TIM3_CH1_PWM_Init(u16 pre,u16 psc)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOC,&GPIO_InitStructure);
	
	GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);		//管脚映射
	
	
	TIM_TimeBaseInitStructure.TIM_Period=pre;					//周期
	TIM_TimeBaseInitStructure.TIM_Prescaler=psc;
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
	
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OC1Init(TIM3,&TIM_OCInitStructure);
	
	TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);
	
	TIM_ARRPreloadConfig(TIM3,ENABLE);
	
	TIM_Cmd(TIM3,ENABLE);
	
}

main.c

#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "time.h"
#include "pwm.h"


int main()
{
	u16 i=0;
	u8 fx=0;
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	LED_Init();
	TIM3_CH1_PWM_Init(500,72-1);
	
	while(1)
	{
		if(fx==0)
		{
			i++;
			if(i%300==0)
			{
				fx=1;
			}
		}
		else
		{
			i--;
			if(i==0)
			{
				fx=0;
			}
		}
		TIM_SetCompare1(TIM3,i);			//调整占空比
		delay_ms(10);	
	}

}

2、内部温度传感器
对于传感器采集到的信息一般都是通过电压的变化来表示,而在实际中我们需要直观的看到这个数据信息,那么就需要一个转换过程,可以将模拟信息转换为数字信息。在STM32单片机中,存在着3个ADC(模数转换器)外设,可以独立地使用,将模拟信号转换为数字信号。STM32中的ADC是一个12位逐次逼近型的模拟数学转换器,其具有18个复用通道,可测量16个外部信号源、2个内部信号源。通道的A/D转换可以以单次、连续、扫描或间断模式执行,结果可以以左对齐或右对齐的方式存储在16位数据寄存器中,外设ADC的内部结构如下图中所示:
在这里插入图片描述

ADC转换的模式有单次转换和连续转换。

单次转换模式下,ADC执行一次转换。通过ADC_CR2寄存器的SWSTART位启动,也可以通过外部触发启动。一旦所选定的通道转换完成,转换结果将被保存在ADC_DR寄存器中,ADC停止,直到下一次被启动。
连续转换模式下,ADC结束一次转换后继续开始下一次的转换。CONT位为1时,通过将ADC_CR2寄存器的SWSTRT位置1或外部触发的方式启动该模式。
对于ADC的库函数配置版如下所示:

使能端口时钟以及ADC时钟,设置引脚模式为模拟输入;
ADCx_INC0~ADCx_IN5为外部通道,分别对应于芯片的一个引脚,一次需要使能GPIOX以及ADCx时钟(挂载在APB2总线上),调用函数:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOX|RCC_APB2Periph_ADCx, ENABLE);
然后将对应的引脚设置为模拟输入模式(用于采集电压信号):
设置ADC的分频因子;
分频因子要保证ADC的时钟(ADCCLK)小于14MHz,ADC的时钟有72/分频因子计算的出,可调用函数:RCC_ADCCLKConfig(RCC_PCLK2_Divx);
初始化ADC参数;
初始化ADC时,需要配置ADC的转换模式、触发方式、数据对齐方式等参数,可调用函数:void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
ADCx用来选择使用哪一个ADC;结构体ADC_InitTypeDef的成员变量如下:

typedef struct
{
		uint32_t ADC_Mode;							//工作模式选择
		FunctionalState ADC_ScanConvMode;				//扫描模式选择
		FunctionalState ADC_ContinuousConvMode;		//转换模式
		uint32_t ADC_ExternalTrigConv;					//触发信号选择
		uint32_t ADC_DataAlign;						//数据对齐方式
		uint8_t ADC_NbrOfChannel;						//采集通道数
}

ADC_Mode:模式选择,独立模式、双重模式;
ADC_ScanConvMode:扫描模式选择,DISABLE(单通道)或ENABLE(多通道);
ADC_ContinuousConvMode:连续转换模式选择,DISABLE(单次)或ENABLE(连续);
ADC_ExternalTrigConv:一般默认软件自动触发,不进行配置;
ADC_DataAlign:数据对齐方式,ADC_DataAlign_Right(右对齐)或ADC_DataAlign_Left(左对齐);
ADC_NbrOfChannel:AD转换通道数目,根据需要选择;

使能ADC并开启校准;
开启ADC并复位校准后,其才能够正常工作,开启ADC时调用函数:void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
执行复位校准:ADC_ResetCalibration(ADCx);
执行ADC校准:ADC_StartCalibration(ADCx);
校准后需要等待校准结束,可通过校验位进行判断:while(ADC_ResetCalibration(ADCx)); while(ADC_StartCalibration(ADCx));
读取ADC转换值;
在读取ADC转换值之前还需要设置规则序列中的通道、采样顺序以及采样周期,然后才启动ADC转换;

调用函数:void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
开启ADC转换时调用函数(软件触发):void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
读取ADC转换值时调用函数:uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
判断ADC的转换是否结束:while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));

2.1 传感器介绍
在STM32F1单片机内部有一个温度传感器,可以被用来测量CPU及周围的温度,它支持的测温范围为-40 oC ~125oC,精度为±1.5 oC。其内部的连接结构如下图中所示:
在这里插入图片描述

在前序内容中已经介绍过了ADC转换的使用,在这里温度传感器的输出信号就是一个电压值的变化,需要通过A/D模数转换成数字信号。对于温度传感器的设置需要考虑到:

激活ADC内部通道,置ADC_CCR的TSVREFE为1;
读取输入端口的电压信号,通过公式将其转换为温度:
T(oC) = ( (V25 - Vsense) / Avg_slope ) + 25;
式中,V25 = Vsense在25 oC时的电压数(1.43V);Avg_slope=温度与Vsense曲线的平均斜率(4.3mV/ oC)
2.2 配置步骤
对于温度传感器的配置,需要用到ADC相关的库文件(stm32f10x_adc.h和stm32f10x_adc.c),具体步骤如下:

初始化ADC参数,开启内部温度控制器,调用函数:ADC_TempSensorVrefintCmd(ENABLE);
读取ADC采集到的AD值,将其转换为对应的温度。
2.3 应用示例
通过STM32芯片内部的温度传感器读取温度值,具体的代码如下所示:


adc.h

#ifndef _adc_temp_H
#define _adc_temp_H

#include "system.h"
void ADC_Temp_Init(void);
int Get_Temperture(void);
#endif

adc.c

#include "adc_temp.h"
#include "SysTick.h"
#include "usart.h"

void ADC_Temp_Init(void)
{
	ADC_InitTypeDef ADC_InitStructure;
	
	//注意时钟的开启顺序
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	
	ADC_TempSensorVrefintCmd(ENABLE);				//开启内部温度传感器通道
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;	/独立工作模式
	ADC_InitStructure.ADC_ScanConvMode=DISABLE;		//单通道采样
	ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;	//单次扫描
	ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//不使用外部触发
	ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;	//数据右对齐方式
	ADC_InitStructure.ADC_NbrOfChannel=1; 				//通道数量
	ADC_Init(ADC1,&ADC_InitStructure);
	
	ADC_Cmd(ADC1,ENABLE);							//使能ADC
	
	ADC_ResetCalibration(ADC1);							//复位校准
	while(ADC_GetResetCalibrationStatus(ADC1));				
	
	ADC_StartCalibration(ADC1);							//开启校准
	while(ADC_GetCalibrationStatus(ADC1));					
	
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);			//软件启动ADC
}
u16 Get_Temp_Value(u8 ch,u8 times)							//获取ADC通道采集回来的数据
{
	u8 t;
	u32 temp_val=0;
	ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5);
	for(t=0;t<times;t++)
	{
		ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//软件启动ADC
		while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
		temp_val+=ADC_GetConversionValue(ADC1);
		delay_ms(5);
	}
	return temp_val/times;
}
int Get_Temperture(void)
{
	u16 temp;
	double tempreture;
	int T;
	temp=Get_Temp_Value(ADC_Channel_16,10);
	printf("%d\r\n",temp);
	tempreture=(float)temp*(3.3/4096);
	tempreture=(1.43-tempreture)/0.0043+25;
	T=tempreture*100;
	printf("%d\r\n",T);
	return T;
}

main.c

#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
#include "adc_temp.h"

int main()
{
	u8 i=0;
	int tempreture;
	
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);		//中断优先级分组
	LED_Init();
	USART1_Init(9600);
	ADC_Temp_Init();

	while(1)
	{
		i++;
		if(i%20==0)
		{
			led1=!led1;
		}
		
		if(i%50==0)
		{
			tempreture=Get_Temperture();
			if(tempreture<0)
			{	
				tempreture=-tempreture;
				printf("节点温度为:-%.2f C\r\n",(float) tempreture/100-1);
			}
			else
			{
				printf("节点温度为:+%.2f C\r\n",(float) tempreture/100-1);			
}
		}
		delay_ms(10);	
	
	}
}

3、RTC实时时钟
3.1 传感器介绍
在STM32中RTC(实时时钟)是一个独立的定时器,其具有一组连续计数的计数器,可提供时钟日历的功能。RTC模块拥有一个后备电源—纽扣电池,因此当主控掉电时,只要纽扣电池有电,RTC就会正常的工作。RTC是一个32位的计数器,采取向上计数模式,其时钟源来自于高速外部时钟的128分频、低速内部时钟LSI以及外部时钟LSE三种。在使用HSE分频时钟或LSI时,主电源掉电时,时钟会受到影响,导致RTC无法正常工作。因此一般情况下,RTC使用LSE时钟。RTC的内部结构图如下所示:
在这里插入图片描述

3.2 配置步骤
对于RTC的配置,需要用到RTC相关的库文件(stm32f10x_rtc.h和stm32f10x_rtc.c),具体步骤如下:

使能电源时钟和后备域时钟,开启RTC后备寄存器写访问;
调用库函数:RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //使能电源时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); //使能后备域时钟 PWR_BackupAccessCmd(ENALE); //使能后备寄存

复位备份区域,开启LSE时钟;
在开启后备寄存器写访问后,首先要对该区域进行复位操作,之后使能LSE时钟,调用函数:BKP_DeInit(); //复位备份区域 RCC_LSEConfig(RCC_LSE_ON); //开启LSE时钟

使能RTC时钟;
选择LSE作为RTC时钟源,之后使能RTC时钟,调用函数:RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE);

设置RTC分频系数,配置RTC参数;
首先要打开RTC允许配置位:RTC_EnterConfigMode();
设置RTC时钟分频系数:void RTC_SetPrescaler(uint32_t PrescalerValue);
设置秒中断允许:void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);
设置RTC计数值:void RTC_Setounter(uint32_t CounterValue);

更新配置,设置RTC中断分组;
调用配置更新函数:RTC_ExitConfigMode();
在备份区域写用户数据:void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
读取备份区域内容:uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
配置中断分组:NVIC_Init

编写RTC中断服务函数。
在STM32中RTC中断函数为:RTC_IRQHandler;
读取RTC状态标志位:FlagStatus RTC_GetFlagStatus(uint32_t RTC_FLAG);
清除RTC秒中断标志:RTC_ClearITPendingBit(RTC_IT_SEC);

3.3 应用示例
设置STM32的RTC时间初始值,进行计时,详细的配置代码如下:
rtc.h

#ifndef _rtc_H
#define _rtc_H

#include "system.h"

typedef struct{
	u8 hour;
	u8 min;
	u8 sec;
}_calendar;

extern _calendar calendar;
void RTC_Get(void);
u8 RTC_Init(void);

#endif

rtc.c

#include "rtc.h"
#include "SysTick.h"
#include "usart.h"


_calendar calendar;

void RTC_NVIC_Config()
{
	NVIC_InitTypeDef NVIC_InitStructure;
	
	NVIC_InitStructure.NVIC_IRQChannel=RTC_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}
void RTC_Get(void)
{
	u32 time=0;
	time=RTC_GetCounter();
	calendar.hour=time/3600;
	calendar.min=time%3600/60;
	calendar.sec=time%60;
}
u8 RTC_Init(void)
{
	u8 temp=0;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP,ENABLE);//使能电源和后备域时钟
	PWR_BackupAccessCmd(ENABLE);//开启RTC后备寄存器访问
	
	if(BKP_ReadBackupRegister(BKP_DR1)!=0xA0A0)//第一次进入RTC
	{
		BKP_DeInit();/后备域寄存器初始化
		RCC_LSEConfig(RCC_LSE_ON);//开启LSE外部晶振
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET&&temp<250)//2.5秒为开启即退出
		{
			temp++;
			delay_ms(10);
		}
		if(temp>=250)/初始化失败,晶振不稳定
		{
			return 1;
		}
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
		RCC_RTCCLKCmd(ENABLE); 
		RTC_WaitForLastTask();
		RTC_WaitForSynchro();
		RTC_ITConfig(RTC_IT_SEC,ENABLE);
		RTC_WaitForLastTask();
		RTC_EnterConfigMode();
		RTC_SetPrescaler(32767);
		RTC_WaitForLastTask();
		RTC_SetCounter(0x11DB4);
		RTC_ExitConfigMode();
		
		BKP_WriteBackupRegister(BKP_DR1,0xA0A);
	}
	else
	{
		
		RTC_WaitForSynchro();
		RTC_ITConfig(RTC_IT_SEC,ENABLE);
		RTC_WaitForLastTask();
	}
	RTC_NVIC_Config();
	RTC_Get();
	return 0;
}
void RTC_IRQHandler(void)
{
	if(RTC_GetITStatus(RTC_IT_SEC)!=RESET)
	{
		RTC_Get();
		printf("RTC Time:%d:%d:%d\t\n",calendar.hour,calendar.min,calendar.sec);
	}
	RTC_ClearITPendingBit(RTC_IT_SEC);
}

main.c

#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
#include "rtc.h"

int main()
{
	u8 i=0;
	
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//ÖжÏÓÅÏȼ¶·Ö×é
	LED_Init();
	USART1_Init(9600);
  	RTC_Init();
	while(1)
	{
		i++;
		if(i%20==0)
		{
			led1=!led1;
		}
		delay_ms(10);
	}
}

4、红外遥控
4.1 传感器介绍
红外遥控是一种利用波长为0.76~1.5μm之间的近红外线来传输控制信号的方法,它是一种无线控制技术,具备较强的抗干扰性、功耗低、成本低以及容易实现的特点。由于红外线无法穿透障碍物,因此对于同类产品的红外线遥控器接收器可以有相同的遥控频率或编码,大大方便了控制系统设计并且易于调试。红外遥控通信系统由红外线发射装置以及红外线接收设备两大部分组成。

4.2 配置步骤
根据红外遥控通信控制系统的组成,对于红外遥控的配置分为如下两部分:

红外发射装置
红外发射装置比较常见的就是遥控器,由键盘电路、红外编码电路、电源电路以及红外发射电路组成。目前被大量使用的遥控器发出的红外线波长在940nm上下,可普通的发光二极管的形状相同,但是红外发光二极管发出光属于不可见光。典型的红外遥控器与红外二极管如下图中所示.

在这里插入图片描述

通常红外遥控为了提高抗干扰性和降低电源消耗,采用了载波的方式传输二进制编码,载波的频率一般为38kHZ由发射端的晶振频率所决定。常用的二进制编码脉冲为NEC Protocol的PWM码和Philips RC-5 Protocol的PWM码。当采用NEC码时,一个脉冲对应560us的连续载波,传输1需要2.25ms(560us的脉冲+1680us的低电平信号),传输0需要1.125ms(560us的脉冲+560us的低电平信号)。NEC遥控指令码的数据格式分为引导码、地址码、地址反码、控制码以及控制反码。引导码是一个9ms的低电平和一个4.5ms的高电平组成;地址码、地址反码、控制码以及控制反码为8位数据格式,按照低位在前、高位在后的顺序发送。NEC中的连发码是有9ms低电平+2.5ms高电平+0.56ms低电平+97.94高电平组成。在按下遥控器的一个按键一帧数据发送后,按键没有松开则发送连发码,通过统计连发码的次数可以计算出按键按下的长短或次数。

红外接收设备
红外接收设备由红外接收电路、红外解码、电源以及应用电路组成。红外接收器的作用是将遥控器发射来的红外光信号转换成电信号,再经过放大、限幅、检波以及整形的步骤形成遥控指令脉冲信号,输送至单片机。一个红外接收头的实物如下图所示:
在这里插入图片描述

红外接收头在没有受到红外脉冲的时候为高电平,收到脉冲时为低电平,因此可以通过外部中断的下降沿触发中断模式在中断服务函数中计算高电平的时间来判断接收到的二进制码时0或1。在STM32的红外遥控接收端的软件程序框架大致如下:

使能IO口以及AFIO时钟,复用引脚IO到外部中断线上,初始化中断系统;
编写中断服务函数,包含红外解码部分;
编写main函数。
4.3 应用示例
利用STM32的中断功能解码红外接收端受到的遥控指令,详细的代码如下:
hw.h

#ifndef _hw_H
#define _hw_H

#include "system.h"

void HW_Init(void);
u8 hw_getHigh(void);

extern u32 hw_jsm;
extern u8 hw_jsbz;

#endif

hw.c

#include "hw.h"
#include "SysTick.h"

u32 hw_jsm; 			//存储接收码
u8 hw_jsbz; 			//接收标志


void HW_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	EXTI_InitTypeDef EXTI_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG|RCC_APB2Periph_AFIO,ENABLE);
	
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_Init(GPIOG,&GPIO_InitStructure);
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOG,GPIO_PinSource15);
	EXTI_ClearITPendingBit(EXTI_Line15);
	
	EXTI_InitStructure.EXTI_Line=EXTI_Line15;
	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=EXTI15_10_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);

}
u8 hw_getHigh()		//得到高电平持续的时间
{
	u8 t=0;
	while(GPIO_ReadInputDataBit(GPIOG,GPIO_Pin_15)==1)
	{
		t++;
		delay_us(20);
		if(t>=250)
			return t;
	}
	return t;
}
void EXTI15_10_IRQHandler(void)
{
	u8 Tim=0,Ok=0,Data,Num=0;
	while(1)
	{
		if(GPIO_ReadInputDataBit(GPIOG,GPIO_Pin_15)==1)
		{
			
			Tim=hw_getHigh();		
			
			if(Tim>=250)				//无效信号
				break;
			if(Tim>=200&&Tim<250)
			{
				Ok=1;				//起始信号
			}
			else if(Tim>=60&&Tim<90)
			{
				Data=1;				/数据1
			}
			else if(Tim>=10&&Tim<50)
			{
				Data=0;				//数据0
			}
			if(Ok==1)
			{
				hw_jsm<<=1;
				hw_jsm+=Data;
				if(Num>=32)
				{
					hw_jsbz=1;
					break;
				}
			}
			Num++;
		}
	}
	EXTI_ClearITPendingBit(EXTI_Line15);
}

main.c

#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
#include "hw.h"

int main()
{
	u8 i=0;
	
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	LED_Init();
	USART1_Init(9600);
	HW_Init();
	while(1)
	{
		if(hw_jsbz==1)
		{
			hw_jsbz=0;
			printf("红外接收码 %0.8X\r\n", hw_jsm);
			hw_jsm=0;
		}
		i++;
		if(i%20==0)
		{
			led1=!led1;
		}
		delay_ms(10);
	}
}